USE [mag_pbi]
GO

/****** Object:  StoredProcedure [analytics].[create_forecast_loop]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [analytics].[create_forecast_loop] as begin
	DECLARE @from_month    DATE = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
	DECLARE @to_month_excl DATE = '2027-01-01';
	DECLARE @scenario_id   INT  = 4;
	DECLARE @path NVARCHAR(255);

	DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
	SELECT [path]
	FROM [pbi].[groups] g
	WHERE [lvl] = 2
	AND g.[path] NOT LIKE '*%'   -- (опционально) исключить служебные группы
	
	/*AND (
		g.[path] LIKE N'Р%' OR
		g.[path] LIKE N'С%' OR
		g.[path] LIKE N'Т%' OR
		g.[path] LIKE N'У%' OR
		g.[path] LIKE N'Ф%' OR
		g.[path] LIKE N'Х%' OR
		g.[path] LIKE N'Ц%' OR
		g.[path] LIKE N'Ч%' OR
		g.[path] LIKE N'Ш%' OR
		g.[path] LIKE N'Щ%' OR
		g.[path] LIKE N'Э%' OR
		g.[path] LIKE N'Ю%' OR
		g.[path] LIKE N'Я%'
	)*/
	ORDER BY [path];

	OPEN cur;
	FETCH NEXT FROM cur INTO @path;

	WHILE @@FETCH_STATUS = 0
	BEGIN
		PRINT CONCAT('Rebuild forecast for: ', @path);
		EXEC [analytics].[sp_build_forecast_s4_by_group]
			 @path          = @path,
			 @from_month    = @from_month,
			 @to_month_excl = @to_month_excl,
			 @scenario_id   = @scenario_id;

		FETCH NEXT FROM cur INTO @path;
	END

	CLOSE cur;
	DEALLOCATE cur;
END
GO

/****** Object:  StoredProcedure [analytics].[create_seasonality_groups]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE analytics.[create_seasonality_groups]  as
BEGIN 
	/* 1) Таблица результата */
	IF NOT EXISTS (
		SELECT 1 
		FROM sys.tables t 
		JOIN sys.schemas s ON s.schema_id = t.schema_id
		WHERE s.name = 'analytics' AND t.name = 'seasonality_groups'
	)
	BEGIN
		CREATE TABLE [analytics].[seasonality_groups](
			[group_1c_id]   BINARY(16)     NOT NULL,
			[month]         TINYINT        NOT NULL,  -- 1..12
			[seasonal_koef] DECIMAL(18,6)  NOT NULL,
			CONSTRAINT [PK_seasonality_groups] PRIMARY KEY ([group_1c_id], [month])
		);
		CREATE INDEX [IX_seasonality_groups_month] 
			ON [analytics].[seasonality_groups]([month]);
	END

	TRUNCATE TABLE [analytics].[seasonality_groups];
	/* 2) Пересчёт коэффициентов сезонности для g и g1 */
	;WITH
	-- Продажи по товарам за последние 24 месяца (кол-во)
	[base_sales] AS (
		SELECT
			[n].[1c_id]                AS [sku_1c_id],
			[gcur].[g],
			[gcur].[g1],
			[v].[Период]               AS [dt],
			CAST([v].[Количество] AS DECIMAL(18,6)) AS [qty]
		FROM [mag_pbi].[pbiProd].[СводныйСебестоимость Для PBI] AS [v]
		JOIN [pbi].[nomenclature] AS [n]
		  ON [n].[1c_id] = [v].[1c_id]
		LEFT JOIN [pbi].[groups] AS [gcur]
		  ON [gcur].[1c_id] = [n].[1c_group]
		WHERE [v].[Статья] = N'Реализация'
		  AND [v].[Период] >= DATEADD(YEAR, -2, CAST(GETDATE() AS date))
	),
	-- Агрегация по месяцам и уровню g (lvl=0)
	[lvl0_monthly] AS (
		SELECT
			[g0].[1c_id] AS [group_1c_id],
			DATEFROMPARTS(YEAR([b].[dt]), MONTH([b].[dt]), 1) AS [month_start],
			SUM([b].[qty]) AS [qty_m]
		FROM [base_sales] AS [b]
		JOIN [pbi].[groups] AS [g0]
		  ON [g0].[lvl] = 1
		 AND [g0].[g] = [b].[g]
		GROUP BY [g0].[1c_id], DATEFROMPARTS(YEAR([b].[dt]), MONTH([b].[dt]), 1)
	),
	-- Агрегация по месяцам и уровню g1 (lvl=1)
	[lvl1_monthly] AS (
		SELECT
			[g1].[1c_id] AS [group_1c_id],
			DATEFROMPARTS(YEAR([b].[dt]), MONTH([b].[dt]), 1) AS [month_start],
			SUM([b].[qty]) AS [qty_m]
		FROM [base_sales] AS [b]
		JOIN [pbi].[groups] AS [g1]
		  ON [g1].[lvl] = 2
		 AND [g1].[g1] = [b].[g1]
		GROUP BY [g1].[1c_id], DATEFROMPARTS(YEAR([b].[dt]), MONTH([b].[dt]), 1)
	),
	-- Объединяем уровни
	[monthly_union] AS (
		SELECT * FROM [lvl0_monthly]
		UNION ALL
		SELECT * FROM [lvl1_monthly]
	),
	-- Средние по «месяцу года»
	[per_moy] AS (
		SELECT
			[u].[group_1c_id],
			MONTH([u].[month_start]) AS [month],
			AVG([u].[qty_m]) AS [avg_qty_moy]
		FROM [monthly_union] AS [u]
		GROUP BY [u].[group_1c_id], MONTH([u].[month_start])
	),
	-- Общая средняя по группе
	[overall] AS (
		SELECT
			[u].[group_1c_id],
			AVG([u].[qty_m]) AS [overall_avg_monthly]
		FROM [monthly_union] AS [u]
		GROUP BY [u].[group_1c_id]
	),
	-- Черновой коэффициент
	[raw_koef] AS (
		SELECT
			[p].[group_1c_id],
			[p].[month],
			CASE 
				WHEN [o].[overall_avg_monthly] = 0 THEN 0
				ELSE [p].[avg_qty_moy] / [o].[overall_avg_monthly]
			END AS [k_raw]
		FROM [per_moy] AS [p]
		JOIN [overall] AS [o]
		  ON [o].[group_1c_id] = [p].[group_1c_id]
	),
	[norm] AS (
		SELECT
			[r].[group_1c_id],
			[r].[month],
			[r].[k_raw] / NULLIF(AVG([r].[k_raw]) OVER (PARTITION BY [r].[group_1c_id]), 0) AS [seasonal_koef]
		FROM [raw_koef] AS [r]
	)
	INSERT INTO [analytics].[seasonality_groups] ([group_1c_id], [month], [seasonal_koef])
	SELECT [group_1c_id], [month], CAST([seasonal_koef] AS DECIMAL(18,6))
	FROM [norm];

END
GO

/****** Object:  StoredProcedure [analytics].[sp_build_deficit_proposal]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/* =======================================================================
   [analytics].[sp_build_deficit_proposal]
   — Дефицит и рекомендации к заказу с учётом:
     • стартовых остатков (склад + МП),
     • прихода из заказов (4 статуса, включая 'Согласован'),
     • прогноза по сценарию,
     • НЕТ бэк-ордеров: спрос не «копится» ниже нуля,
       остаток считается итеративно и не уходит в минус,
     • размер заказа = прогноз окна [T .. T + @cover_months).
   ======================================================================= */
CREATE PROCEDURE [analytics].[sp_build_deficit_proposal]
    @scenario_id    INT               = 4,              -- ID сценария прогноза
    @group_path     NVARCHAR(255)     = N'',            -- path группы ('' = весь каталог)
    @lead_time_m    INT               = 4,              -- срок поставки (мес.)
    @cover_months   INT               = 6,              -- покрытие (мес.)
    @from_month     DATE              = NULL,           -- старт (1-е число месяца)
    @to_month_excl  DATE              = '2028-01-01',   -- полуинтервал [from, to)
    @debug          BIT               = 0
AS
BEGIN
    SET NOCOUNT ON;

    /* 0) Чистим прошлые данные по сценарию */
    DELETE FROM [analytics].[deficit_proposal] WHERE scenario_id = @scenario_id;

    /* Нормализация параметров */
    SET @group_path = LTRIM(RTRIM(REPLACE(@group_path, '''', N'')));
    IF @from_month IS NULL
        SET @from_month = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);

    /* 1) Календарь */
    IF OBJECT_ID('tempdb..#cal') IS NOT NULL DROP TABLE #cal;
    CREATE TABLE #cal (month_start DATE NOT NULL PRIMARY KEY);
    ;WITH cal AS (
        SELECT @from_month AS month_start
        UNION ALL
        SELECT DATEADD(MONTH, 1, month_start)
        FROM cal
        WHERE month_start < DATEADD(MONTH, -1, @to_month_excl)
    )
    INSERT INTO #cal(month_start)
    SELECT month_start FROM cal
    OPTION (MAXRECURSION 32767);

    /* 2) SKU по group_path */
    IF OBJECT_ID('tempdb..#skus') IS NOT NULL DROP TABLE #skus;
    CREATE TABLE #skus (
        sku_1c_id BINARY(16) NOT NULL PRIMARY KEY,
        code      NVARCHAR(36) NULL,
        minAvail  DECIMAL(18,6) NULL
    );
    INSERT INTO #skus(sku_1c_id, code, minAvail)
    SELECT n.[1c_id], n.[code], n.[minAvailableQty]
    FROM [pbi].[nomenclature] n
    JOIN [pbi].[groups] g ON g.[1c_id] = n.[1c_group]
    WHERE g.[path] LIKE @group_path + N'%';

    IF NOT EXISTS (SELECT 1 FROM #skus)
    BEGIN
        RAISERROR(N'По path=%s не найдено SKU.', 16, 1, @group_path);
        RETURN;
    END

    /* 3) Прогноз по сценарию */
    IF OBJECT_ID('tempdb..#fcast') IS NOT NULL DROP TABLE #fcast;
    CREATE TABLE #fcast (
        sku_1c_id BINARY(16) NOT NULL,
        [month]   DATE NOT NULL,
        qty       DECIMAL(18,3) NOT NULL,
        PRIMARY KEY (sku_1c_id, [month])
    );
    INSERT INTO #fcast(sku_1c_id, [month], qty)
    SELECT f.[1c_id], f.[month], f.[value]
    FROM [analytics].[forecast] f
    JOIN #skus s ON s.sku_1c_id = f.[1c_id]
    WHERE f.[scenario_id] = @scenario_id
      AND f.[month] >= @from_month
      AND f.[month] <  @to_month_excl;

    /* 4) Приходы из заказов (по месяцам) */
    IF OBJECT_ID('tempdb..#inb_status') IS NOT NULL DROP TABLE #inb_status;
    CREATE TABLE #inb_status (
        sku_1c_id BINARY(16) NOT NULL,
        [month]   DATE NOT NULL,
        [status]  NVARCHAR(100) NOT NULL,
        units     DECIMAL(18,3) NOT NULL,
        PRIMARY KEY (sku_1c_id, [month], [status])
    );
    INSERT INTO #inb_status(sku_1c_id, [month], [status], units)
    SELECT
        s.sku_1c_id,
        DATEFROMPARTS(TRY_CONVERT(INT, LEFT(o.[month], 4)),
                      TRY_CONVERT(INT, SUBSTRING(o.[month], 6, 2)), 1),
        o.[status],
        SUM(COALESCE(o.[units], 0.0))
    FROM [analytics].[get_orders_by_group] o
    JOIN #skus s ON s.code = o.[code]
    WHERE o.[status] IN (N'В пути', N'В производстве', N'Выгружен на складе'/*, N'Согласован'*/)
      AND TRY_CONVERT(INT, LEFT(o.[month], 4)) IS NOT NULL
      AND TRY_CONVERT(INT, SUBSTRING(o.[month], 6, 2)) BETWEEN 1 AND 12
    GROUP BY s.sku_1c_id,
             DATEFROMPARTS(TRY_CONVERT(INT, LEFT(o.[month], 4)),
                           TRY_CONVERT(INT, SUBSTRING(o.[month], 6, 2)), 1),
             o.[status];

    IF OBJECT_ID('tempdb..#inb') IS NOT NULL DROP TABLE #inb;
    CREATE TABLE #inb (
        sku_1c_id BINARY(16) NOT NULL,
        [month]   DATE NOT NULL,
        units     DECIMAL(18,3) NOT NULL,
        PRIMARY KEY (sku_1c_id, [month])
    );
    INSERT INTO #inb(sku_1c_id, [month], units)
    SELECT sku_1c_id, [month], SUM(units)
    FROM #inb_status
    WHERE [month] >= @from_month AND [month] < @to_month_excl
    GROUP BY sku_1c_id, [month];

    /* 5) Стартовые остатки: склад + МП */
    IF OBJECT_ID('tempdb..#stock_base') IS NOT NULL DROP TABLE #stock_base;
    CREATE TABLE #stock_base (sku_1c_id BINARY(16) NOT NULL PRIMARY KEY, qty DECIMAL(18,3) NOT NULL);
    INSERT INTO #stock_base(sku_1c_id, qty)
    SELECT s.sku_1c_id, COALESCE(b.qty, 0.0)
    FROM #skus s
    LEFT JOIN (
        SELECT code, SUM(COALESCE(quantity_base,0.0)) AS qty
        FROM [analytics].[get_quantity_by_group]
        GROUP BY code
    ) b ON b.code = s.code;

    IF OBJECT_ID('tempdb..#stock_mp') IS NOT NULL DROP TABLE #stock_mp;
    CREATE TABLE #stock_mp (sku_1c_id BINARY(16) NOT NULL PRIMARY KEY, qty DECIMAL(18,3) NOT NULL);
    INSERT INTO #stock_mp(sku_1c_id, qty)
    SELECT s.sku_1c_id, COALESCE(m.qty, 0.0)
    FROM #skus s
    LEFT JOIN (
        SELECT code, SUM(COALESCE(quantity_base,0.0)) AS qty
        FROM [analytics].[get_mp_quantity_by_group]
        GROUP BY code
    ) m ON m.code = s.code;

    IF OBJECT_ID('tempdb..#stock0') IS NOT NULL DROP TABLE #stock0;
    CREATE TABLE #stock0 (sku_1c_id BINARY(16) NOT NULL PRIMARY KEY, qty DECIMAL(18,3) NOT NULL);
    INSERT INTO #stock0(sku_1c_id, qty)
    SELECT s.sku_1c_id, COALESCE(b.qty,0) + COALESCE(mp.qty,0)
    FROM #skus s
    LEFT JOIN #stock_base b ON b.sku_1c_id = s.sku_1c_id
    LEFT JOIN #stock_mp   mp ON mp.sku_1c_id = s.sku_1c_id;

    /* 6) Лента (сырой спрос/приход) */
    IF OBJECT_ID('tempdb..#tl') IS NOT NULL DROP TABLE #tl;
    CREATE TABLE #tl (
        sku_1c_id BINARY(16) NOT NULL,
        [month]   DATE NOT NULL,
        demand_m  DECIMAL(18,3) NOT NULL,
        inb_m     DECIMAL(18,3) NOT NULL,
        cum_d     DECIMAL(38,3) NULL,   -- кумулятив обслуженного спроса
        cum_i     DECIMAL(38,3) NULL,   -- кумулятив прихода
        net_stock DECIMAL(38,3) NULL,   -- итоговый остаток месяца (>=0)
        served_m  DECIMAL(18,3) NULL,   -- обслуженный спрос в месяце
        lost_m    DECIMAL(18,3) NULL,   -- потерянный спрос в месяце
        PRIMARY KEY (sku_1c_id, [month])
    );
    INSERT INTO #tl(sku_1c_id, [month], demand_m, inb_m)
    SELECT s.sku_1c_id, c.month_start,
           COALESCE(f.qty, 0.0),
           COALESCE(i.units, 0.0)
    FROM #skus s
    CROSS JOIN #cal c
    LEFT JOIN #fcast f ON f.sku_1c_id = s.sku_1c_id AND f.[month] = c.month_start
    LEFT JOIN #inb   i ON i.sku_1c_id = s.sku_1c_id AND i.[month] = c.month_start;

    /* 6.1) Пронумеруем месяцы (для рекурсивного расчёта без бэк-ордеров) */
    IF OBJECT_ID('tempdb..#seq') IS NOT NULL DROP TABLE #seq;
    CREATE TABLE #seq (
        sku_1c_id BINARY(16) NOT NULL,
        [month]   DATE       NOT NULL,
        demand_m  DECIMAL(18,3) NOT NULL,
        inb_m     DECIMAL(18,3) NOT NULL,
        rn        INT NOT NULL,
        PRIMARY KEY (sku_1c_id, rn)
    );
    INSERT INTO #seq(sku_1c_id, [month], demand_m, inb_m, rn)
    SELECT t.sku_1c_id, t.[month], t.demand_m, t.inb_m,
           ROW_NUMBER() OVER (PARTITION BY t.sku_1c_id ORDER BY t.[month])
    FROM #tl t;

    /* 6.2) Рекурсивный расчёт: served/lost/net_stock (без накопления спроса ниже нуля) */
    IF OBJECT_ID('tempdb..#flow') IS NOT NULL DROP TABLE #flow;
    CREATE TABLE #flow(
        sku_1c_id BINARY(16) NOT NULL,
        rn        INT NOT NULL,
        [month]   DATE NOT NULL,
        demand_m  DECIMAL(18,3) NOT NULL,
        inb_m     DECIMAL(18,3) NOT NULL,
        served_m  DECIMAL(18,3) NOT NULL,
        lost_m    DECIMAL(18,3) NOT NULL,
        net_stock DECIMAL(38,3) NOT NULL,
        PRIMARY KEY (sku_1c_id, rn)
    );

    ;WITH r AS (
        /* первый месяц */
        SELECT
            s.sku_1c_id, s.rn, s.[month], s.demand_m, s.inb_m,
            CAST(
                CASE WHEN st.qty + s.inb_m >= s.demand_m
                     THEN s.demand_m
                     ELSE st.qty + s.inb_m
                END
            AS DECIMAL(18,3)) AS served_m,
            CAST(
                CASE WHEN st.qty + s.inb_m >= s.demand_m
                     THEN 0.0
                     ELSE s.demand_m - (st.qty + s.inb_m)
                END
            AS DECIMAL(18,3)) AS lost_m,
            CAST(
                CASE WHEN st.qty + s.inb_m - s.demand_m < 0
                     THEN 0
                     ELSE st.qty + s.inb_m - s.demand_m
                END
            AS DECIMAL(38,3)) AS net_stock
        FROM #seq s
        JOIN #stock0 st ON st.sku_1c_id = s.sku_1c_id
        WHERE s.rn = 1

        UNION ALL

        /* последующие месяцы */
        SELECT
            s.sku_1c_id, s.rn, s.[month], s.demand_m, s.inb_m,
            CAST(
                CASE WHEN r.net_stock + s.inb_m >= s.demand_m
                     THEN s.demand_m
                     ELSE r.net_stock + s.inb_m
                END
            AS DECIMAL(18,3)) AS served_m,
            CAST(
                CASE WHEN r.net_stock + s.inb_m >= s.demand_m
                     THEN 0.0
                     ELSE s.demand_m - (r.net_stock + s.inb_m)
                END
            AS DECIMAL(18,3)) AS lost_m,
            CAST(
                CASE WHEN r.net_stock + s.inb_m - s.demand_m < 0
                     THEN 0
                     ELSE r.net_stock + s.inb_m - s.demand_m
                END
            AS DECIMAL(38,3)) AS net_stock
        FROM r
        JOIN #seq s
          ON s.sku_1c_id = r.sku_1c_id
         AND s.rn = r.rn + 1
    )
    INSERT INTO #flow
    SELECT * FROM r
    OPTION (MAXRECURSION 32767);

    /* 6.3) Обновим #tl: кумулятивы по обслуженному спросу/приходу и итоговый остаток */
    ;WITH cd AS (
        SELECT f.sku_1c_id, s.[month],
               SUM(f.served_m) OVER (PARTITION BY f.sku_1c_id ORDER BY f.rn ROWS UNBOUNDED PRECEDING) AS cum_d_served,
               SUM(s.inb_m)    OVER (PARTITION BY s.sku_1c_id ORDER BY s.rn ROWS UNBOUNDED PRECEDING) AS cum_i_raw,
               f.net_stock, f.served_m, f.lost_m
        FROM #flow f
        JOIN #seq  s ON s.sku_1c_id = f.sku_1c_id AND s.rn = f.rn
    )
    UPDATE t
       SET t.cum_d     = cd.cum_d_served,
           t.cum_i     = cd.cum_i_raw,
           t.net_stock = cd.net_stock,
           t.served_m  = cd.served_m,
           t.lost_m    = cd.lost_m
    FROM #tl t
    JOIN cd ON cd.sku_1c_id = t.sku_1c_id AND cd.[month] = t.[month];

    /* 7) Первый месяц дефицита T (учитываем lead_time) */
    DECLARE @start_T DATE =
        DATEFROMPARTS(YEAR(DATEADD(MONTH, @lead_time_m, @from_month)),
                      MONTH(DATEADD(MONTH, @lead_time_m, @from_month)), 1);

    IF OBJECT_ID('tempdb..#first_def') IS NOT NULL DROP TABLE #first_def;
    CREATE TABLE #first_def (sku_1c_id BINARY(16) NOT NULL PRIMARY KEY, T DATE NULL);

    INSERT INTO #first_def(sku_1c_id, T)
    SELECT d.sku_1c_id, MIN(d.[month]) AS T
    FROM (
        SELECT t.sku_1c_id, t.[month]
        FROM #tl t
        JOIN #skus s ON s.sku_1c_id = t.sku_1c_id
        WHERE t.net_stock < COALESCE(s.minAvail, 0.0)
          AND t.[month]   >= @start_T
    ) d
    GROUP BY d.sku_1c_id;

    /* 8) Якоря: T, T+C, T+2C ... */
    IF OBJECT_ID('tempdb..#anchors') IS NOT NULL DROP TABLE #anchors;
    CREATE TABLE #anchors (
        sku_1c_id BINARY(16) NOT NULL,
        T DATE NOT NULL,
        PRIMARY KEY (sku_1c_id, T)
    );
    ;WITH recur AS (
        SELECT fd.sku_1c_id, fd.T
        FROM #first_def fd
        WHERE fd.T IS NOT NULL

        UNION ALL
        SELECT r.sku_1c_id, DATEADD(MONTH, @cover_months, r.T)
        FROM recur r
        WHERE DATEADD(MONTH, @cover_months, r.T) < @to_month_excl
    )
    INSERT INTO #anchors(sku_1c_id, T)
    SELECT sku_1c_id, T
    FROM recur
    OPTION (MAXRECURSION 32767);

    /* 9) Подготовка строк к вставке (размер заказа = прогноз окна) */
    IF OBJECT_ID('tempdb..#rows_to_insert') IS NOT NULL DROP TABLE #rows_to_insert;
    CREATE TABLE #rows_to_insert (
        [scenario_id] INT,
        [group_name]  NVARCHAR(255),
        [1c_id]       BINARY(16),
        [code]        NVARCHAR(36),
        [place_month] DATE,
        [arrival_month] DATE,
        [demand_window_C] DECIMAL(18,3),
        [projected_stock_at_T] DECIMAL(18,3),
        [order_qty] DECIMAL(18,3)
    );

    ;WITH base AS (
        SELECT
            s.sku_1c_id,
            s.code,
            a.T,
            st.qty AS stock0,

            /* прогноз окна [T .. T+C) */
            (SELECT SUM(demand_m)
             FROM #tl z
             WHERE z.sku_1c_id = s.sku_1c_id
               AND z.[month] >= a.T
               AND z.[month] <  DATEADD(MONTH, @cover_months, a.T)
            ) AS demand_window,

            /* кумулятивы до T-1 (обслуженный спрос и приход) */
            (SELECT TOP (1) z.cum_i
             FROM #tl z
             WHERE z.sku_1c_id = s.sku_1c_id
               AND z.[month]   < a.T
             ORDER BY z.[month] DESC
            ) AS cum_i_before,

            (SELECT TOP (1) z.cum_d
             FROM #tl z
             WHERE z.sku_1c_id = s.sku_1c_id
               AND z.[month]   < a.T
             ORDER BY z.[month] DESC
            ) AS cum_d_before,

            /* приход именно в T */
            (SELECT SUM(inb_m)
             FROM #tl z
             WHERE z.sku_1c_id = s.sku_1c_id
               AND z.[month]   = a.T
            ) AS inb_t
        FROM #anchors a
        JOIN #skus   s  ON s.sku_1c_id  = a.sku_1c_id
        JOIN #stock0 st ON st.sku_1c_id = s.sku_1c_id
    )
    INSERT INTO #rows_to_insert
    SELECT
        @scenario_id,
        @group_path,
        b.sku_1c_id,
        b.code,
        DATEADD(MONTH, -@lead_time_m, b.T) AS place_month,
        b.T AS arrival_month,
        CAST(ISNULL(b.demand_window,0) AS DECIMAL(18,3)) AS demand_window_C,

        /* старт на T (после прихода T), снизу 0 */
        CAST(
            CASE
                WHEN ( ISNULL(b.stock0,0)
                     + ISNULL(b.cum_i_before,0)
                     + ISNULL(b.inb_t,0)
                     - ISNULL(b.cum_d_before,0) ) < 0
                    THEN 0
                ELSE ( ISNULL(b.stock0,0)
                     + ISNULL(b.cum_i_before,0)
                     + ISNULL(b.inb_t,0)
                     - ISNULL(b.cum_d_before,0) )
            END
        AS DECIMAL(18,3)) AS projected_stock_at_T,

        /* размер заказа = прогноз окна */
        CAST(ISNULL(b.demand_window,0) AS DECIMAL(18,3)) AS order_qty
    FROM base AS b;

    /* 10) Вставка рекомендаций (незначащие — отбрасываем) */
    INSERT INTO [analytics].[deficit_proposal]
        ([scenario_id], [group_name], [1c_id], [code],
         [place_month], [arrival_month],
         [demand_window_C], [projected_stock_at_T], [order_qty],
         [updated_at])
    SELECT
        scenario_id, group_name, [1c_id], [code],
        [place_month], [arrival_month],
        [demand_window_C], [projected_stock_at_T], [order_qty],
        GETDATE()
    FROM #rows_to_insert
    WHERE [order_qty] > 0;

    /* 11) Отладка по желанию */
    IF @debug = 1
    BEGIN
        SELECT TOP (200)
               r.[code], r.[arrival_month], r.[place_month],
               r.[projected_stock_at_T], r.[demand_window_C], r.[order_qty]
        FROM #rows_to_insert r
        ORDER BY r.[arrival_month], r.[code];
    END
END
GO

/****** Object:  StoredProcedure [analytics].[sp_build_forecast_s4_by_group]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [analytics].[sp_build_forecast_s4_by_group]
    @path               NVARCHAR(255),              -- путь группы (например, N'Игрушки')
    @from_month         DATE = NULL,                -- по умолчанию: 1-е число текущего месяца
    @to_month_excl      DATE = '2028-01-01',        -- полуинтервал [from, to)
    @scenario_id        INT  = 4,
    @allow_fallback_all BIT  = 0                    -- 1 = если группа не найдена/пустая → считать весь каталог с дефолтной сезонностью
AS
BEGIN
    SET NOCOUNT ON;

    ----------------------------------------------------------------
    -- Нормализация пути: убираем лишние кавычки и пробелы
    ----------------------------------------------------------------
    SET @path = LTRIM(RTRIM(REPLACE(@path, '''', N'')));

    IF @from_month IS NULL
        SET @from_month = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);

    ----------------------------------------------------------------
    -- Идентификаторы групп для сезонности
    ----------------------------------------------------------------
    DECLARE @season_group_id   BINARY(16) = NULL;
    DECLARE @DEFAULT_GROUP_ID  BINARY(16) = 0x00000000000000000000000000000000;

    -- Пытаемся найти группу ровно по path
    SELECT @season_group_id = g.[1c_id]
    FROM [pbi].[groups] g
    WHERE g.[path] = @path;

    -- Если нет точного совпадения, ищем верхний узел, начинающийся с @path
    IF @season_group_id IS NULL
    BEGIN
        SELECT TOP (1) @season_group_id = g.[1c_id]
        FROM [pbi].[groups] g
        WHERE g.[path] LIKE @path + N'%'
        ORDER BY g.[lvl] ASC;
    END

    -- Если группу так и не нашли: по умолчанию НЕ уходим на весь каталог (защита от опечаток)
    IF @season_group_id IS NULL AND @allow_fallback_all = 0
    BEGIN
        RAISERROR (N'Группа с path = %s не найдена. Проверьте параметр @path или запустите с @allow_fallback_all=1 для расчёта по всему каталогу.', 16, 1, @path);
        RETURN;
    END

    -- Если всё-таки нужно фолбэкнуть на дефолтную сезонность
    IF @season_group_id IS NULL AND @allow_fallback_all = 1
        SET @season_group_id = @DEFAULT_GROUP_ID;

    ----------------------------------------------------------------
    -- TEMP: календарь месяцев [from, to)
    ----------------------------------------------------------------
    IF OBJECT_ID('tempdb..#cal') IS NOT NULL DROP TABLE #cal;
    CREATE TABLE #cal (month_start DATE NOT NULL PRIMARY KEY);

    ;WITH cal AS (
        SELECT @from_month AS month_start
        UNION ALL
        SELECT DATEADD(MONTH, 1, month_start)
        FROM cal
        WHERE month_start < DATEADD(MONTH, -1, @to_month_excl)
    )
    INSERT INTO #cal(month_start)
    SELECT month_start FROM cal
    OPTION (MAXRECURSION 32767);

    ----------------------------------------------------------------
    -- TEMP: SKU под деревом @path; если пусто и разрешён фолбэк → все SKU
    ----------------------------------------------------------------
    IF OBJECT_ID('tempdb..#skus') IS NOT NULL DROP TABLE #skus;
    CREATE TABLE #skus (
        sku_1c_id  BINARY(16) NOT NULL PRIMARY KEY,
        code       NVARCHAR(36) NULL
    );

    INSERT INTO #skus(sku_1c_id, code)
    SELECT n.[1c_id], n.[code]
    FROM [pbi].[nomenclature] n
    JOIN [pbi].[groups] g ON g.[1c_id] = n.[1c_group]
    WHERE g.[path] LIKE @path + N'%'
    OPTION (RECOMPILE);

    IF NOT EXISTS (SELECT 1 FROM #skus)
    BEGIN
        IF @allow_fallback_all = 1
        BEGIN
            INSERT INTO #skus(sku_1c_id, code)
            SELECT n.[1c_id], n.[code]
            FROM [pbi].[nomenclature] n;
            SET @season_group_id = @DEFAULT_GROUP_ID;
        END
        ELSE
        BEGIN
            RAISERROR (N'По path = %s не найдено ни одного SKU. Расчёт не выполнялся.', 16, 1, @path);
            RETURN;
        END
    END

    ----------------------------------------------------------------
    -- TEMP: ставки продаж (шт/день)
    ----------------------------------------------------------------
    IF OBJECT_ID('tempdb..#rate') IS NOT NULL DROP TABLE #rate;
    CREATE TABLE #rate (
        sku_1c_id    BINARY(16) NOT NULL PRIMARY KEY,
        rate_per_day DECIMAL(38,6) NOT NULL
    );

    INSERT INTO #rate(sku_1c_id, rate_per_day)
    SELECT a.[1c_id], ISNULL(a.[Продажи шт / день],0)
    FROM [analytics].[аналитика за 365 дн.] a
    JOIN #skus s ON s.sku_1c_id = a.[1c_id];

    ----------------------------------------------------------------
    -- TEMP: сезонность ТОЛЬКО по @season_group_id; если нет строк — используем дефолт
    ----------------------------------------------------------------
    IF OBJECT_ID('tempdb..#season') IS NOT NULL DROP TABLE #season;
    CREATE TABLE #season (
        [month]        TINYINT NOT NULL PRIMARY KEY,   -- 1..12
        seasonal_koef  DECIMAL(18,6) NOT NULL
    );

    INSERT INTO #season([month], seasonal_koef)
    /*SELECT sg.[month], sg.[seasonal_koef]
    FROM [analytics].[seasonality_groups] sg
    WHERE sg.[group_1c_id] = @season_group_id;*/

	SELECT
		[month]
		, AVG([koef])
	FROM [mag_pbi].[analytics].[seasonality_groups_summ_1]
	GROUP BY [month];

    /*IF @@ROWCOUNT = 0
    BEGIN
        INSERT INTO #season([month], seasonal_koef)
        SELECT sg.[month], sg.[seasonal_koef]
        FROM [analytics].[seasonality_groups] sg
        WHERE sg.[group_1c_id] = @DEFAULT_GROUP_ID;
        -- если и дефолта нет, #season останется пустой → в формуле будет 1.0
    END
	*/
    ----------------------------------------------------------------
    -- Удаляем старые строки сценария (батчами, чтобы не держать тяжёлые блокировки)
    ----------------------------------------------------------------
    WHILE 1 = 1
    BEGIN
        DELETE TOP (50000) f
        FROM [analytics].[forecast] f
        JOIN #skus s ON s.sku_1c_id = f.[1c_id]
        WHERE f.[scenario_id] = @scenario_id
          AND f.[month] >= @from_month
          AND f.[month] <  @to_month_excl;

        IF @@ROWCOUNT = 0 BREAK;
    END

    ----------------------------------------------------------------
    -- Вставка прогноза: rate_per_day × дни_в_месяце × seasonal_koef(@path/@default)
    ----------------------------------------------------------------
    INSERT INTO [analytics].[forecast]
        ([scenario_id], [1c_id], [code], [month], [value], [updated_at], [updated_by])
    SELECT
        @scenario_id,
        s.sku_1c_id,
        s.code,
        c.month_start,
        CAST(ISNULL(r.rate_per_day, 0)
             * DAY(EOMONTH(c.month_start))
             * ISNULL(se.seasonal_koef, 1.0) AS DECIMAL(18,3)) AS value,
        GETDATE(),
        SUSER_SNAME()
    FROM #skus s
    CROSS JOIN #cal c
    LEFT JOIN #rate   r  ON r.sku_1c_id = s.sku_1c_id
    LEFT JOIN #season se ON se.[month] = MONTH(c.month_start)
    OPTION (RECOMPILE);
END
GO

/****** Object:  StoredProcedure [analytics].[sp_create_analytics_365]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [analytics].[sp_create_analytics_365]
AS
BEGIN
    /*
    ... твой закомментированный блок без изменений ...
    */

    -------------------------------------------------------------
    -- 1) Базовая таблица аналитики за 365 дней
    -------------------------------------------------------------
    DROP TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]

    SELECT
        s.[1c_id],
        s.Code,
        SUM(s.Количество) as [Продано шт]
    INTO [mag_pbi].[analytics].[аналитика за 365 дн.]
    FROM [pbiProd].[СводныйСебестоимость Для PBI] s
    WHERE s.Статья = N'Реализация'
      AND s.[Период] >= DATEADD(day, -365, GETDATE())
    GROUP BY [1c_id], s.Code;


    -------------------------------------------------------------
    -- 2) Продажи шт / день
    -------------------------------------------------------------
    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Продажи шт / день') IS NULL
    BEGIN
        ALTER TABLE [analytics].[аналитика за 365 дн.]
        ADD [Продажи шт / день] numeric(38,6) NULL;
        /*ALTER TABLE [analytics].[аналитика за 365 дн.]
        ADD [Продажи шт / день опт] numeric(38,6) NULL;*/
    END;

    ;WITH bounds AS (
        SELECT
            CAST(DATEADD(day, -365, CAST(GETDATE() AS date)) AS date) AS start_date,
            CAST(GETDATE() AS date) AS end_date
    ),
    days365 AS (
        SELECT
            t.[_IDRREF] AS [1c_id],
            SUM(t.[ostatok]) AS days_on_sale_365
            --SUM(t.[ostatok10]) AS days_on_sale_365_opt
        FROM [pbi].[w_ostatok_da_net] t
        CROSS JOIN bounds b
        WHERE t.[dt] >= b.start_date
          AND t.[dt] <  b.end_date      -- исключаем текущий день
        GROUP BY t.[_IDRREF]
    )
    UPDATE a
    SET 
    
        [Продажи шт / день] =
        CASE
            WHEN d.days_on_sale_365 > 0
                THEN CAST(a.[Продано шт] AS numeric(38,6)) / CAST(d.days_on_sale_365 AS numeric(38,6))
            ELSE NULL
        END

        /*[Продажи шт / день опт] =
        CASE
            WHEN d.days_on_sale_365_opt > 0
                THEN CAST(a.[Продано шт] AS numeric(38,6)) / CAST(d.days_on_sale_365_opt AS numeric(38,6))
            ELSE NULL
        END*/

    FROM [analytics].[аналитика за 365 дн.] a
    LEFT JOIN days365 d
        ON d.[1c_id] = a.[1c_id];

   

    -------------------------------------------------------------
    -- 3) Остаток дней продаж
    -------------------------------------------------------------
    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Остаток дней продаж') IS NULL
    BEGIN
        ALTER TABLE [analytics].[аналитика за 365 дн.]
        ADD [Остаток дней продаж] numeric(38,6) NULL;
        /*ALTER TABLE [analytics].[аналитика за 365 дн.]
        ADD [Остаток дней продаж опт] numeric(38,6) NULL;*/
    END;

    UPDATE a
    SET 
        [Остаток дней продаж] =
        CASE 
            WHEN a.[Продажи шт / день] > 0 
                 THEN sb.[Остаток склад + МП, шт] / a.[Продажи шт / день]
            ELSE NULL
        END

        /*[Остаток дней продаж опт] =
        CASE 
            WHEN a.[Продажи шт / день опт] > 0 
                 THEN sb.[Остаток склад + МП, шт] / a.[Продажи шт / день опт]
            ELSE NULL
        END*/
    FROM [analytics].[аналитика за 365 дн.] a
    LEFT JOIN (
        SELECT 
            n.[1c_id],
            s.[Остаток склад + МП, шт]
        FROM [mag_pbi].[analytics].[stock_balance] s
        INNER JOIN [mag_pbi].[pbi].[nomenclature] n
            ON n.artic_id = s.artic_id
    ) sb
        ON sb.[1c_id] = a.[1c_id];




    -------------------------------------------------------------
    -- 4) Сумма продаж, учетка, ТН (год)
    -------------------------------------------------------------
    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Продажи / год, руб.') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Продажи / год, руб.] decimal(38,2) NULL;
    END;

    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'учетная сумма / год, руб.') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [учетная сумма / год, руб.] decimal(38,2) NULL;
    END;

    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'ТН / год, руб.') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [ТН / год, руб.] decimal(38,2) NULL;
    END;

    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Стоимость МП год, руб.') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Стоимость МП год, руб.] decimal(38,2) NULL;
    END;

    IF COL_LENGTH('analytics.аналитика за 365 дн.', '%ТН год, руб.') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [%ТН год, руб.] decimal(38,2) NULL;
    END;

    DECLARE @end   date = CAST(GETDATE() AS date);      -- сегодня (не включаем)
    DECLARE @start date = DATEADD(day, -365, @end);     -- 365 дней назад

    ;WITH Y AS (
        SELECT
            v.[1c_id],
            SUM(v.[Сумма продаж])        AS sales_year,
            SUM(v.[Учетная сумма])       AS acct_year,
            SUM(v.[Стоимость обработки]) AS cost_mp,
            SUM(v.[Торговая надбавка])   AS tn_year
        FROM [analytics].[Продажи_Учёт_Маржа_по_дням] v
        WHERE v.[d] >= @start AND v.[d] < @end
        GROUP BY v.[1c_id]
    )
    UPDATE a
    SET
        a.[Продажи / год, руб.]        = COALESCE(Y.sales_year, 0),
        a.[учетная сумма / год, руб.]  = COALESCE(Y.acct_year, 0),
        a.[ТН / год, руб.]             = COALESCE(Y.tn_year, 0),
        a.[Стоимость МП год, руб.]     = COALESCE(Y.cost_mp, 0),
        a.[%ТН год, руб.] = CASE 
            WHEN NULLIF(COALESCE(Y.acct_year, 0), 0) IS NULL THEN NULL
            ELSE 
                CAST(COALESCE(Y.tn_year, 0) AS decimal(19,6))
                / CAST(NULLIF(Y.acct_year, 0) AS decimal(19,6))
        END
    FROM [mag_pbi].[analytics].[аналитика за 365 дн.] AS a
    LEFT JOIN Y
      ON Y.[1c_id] = a.[1c_id];


    -------------------------------------------------------------
    -- 5) Дней в продаже / год и / квартал
    -------------------------------------------------------------
    SET @end = CAST(GETDATE() AS date);

    DECLARE @startY date = DATEADD(day,  -365, @end);
    DECLARE @startQ date = DATEADD(month, -3, @end);

    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Дней в продаже / год') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Дней в продаже / год] bigint NULL;
    END;

    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Дней в продаже / квартал') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Дней в продаже / квартал] bigint NULL;
    END;
    
    ;WITH daysY AS (
        SELECT [_IDRREF] AS [1c_id],
               COUNT_BIG(*) AS days_in_sale_year
        FROM [pbi].[w_ostatok_da_net] 
        WHERE [dt] >= @startY AND [dt] < @end
          AND [ostatok] >= 1
        GROUP BY [_IDRREF]
    ),
    daysQ AS (
        SELECT [_IDRREF] AS [1c_id],
               COUNT_BIG(*) AS days_in_sale_quarter
        FROM [pbi].[w_ostatok_da_net]
        WHERE [dt] >= @startQ AND [dt] < @end
          AND [ostatok] >= 1
        GROUP BY [_IDRREF]
    ),
    days AS (
        SELECT COALESCE(y.[1c_id], q.[1c_id]) AS [1c_id],
               y.days_in_sale_year,
               q.days_in_sale_quarter
        FROM daysY y
        FULL OUTER JOIN daysQ q ON q.[1c_id] = y.[1c_id]
    )
    UPDATE a
    SET
        a.[Дней в продаже / год]      = d.days_in_sale_year,
        a.[Дней в продаже / квартал]  = d.days_in_sale_quarter
    FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a
    LEFT JOIN days d
      ON d.[1c_id] = a.[1c_id];

    -------------------------------------------------------------
    -- 6) Сумма продаж, учетка, ТН (квартал)
    -------------------------------------------------------------
    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Продажи / квартал, руб.') IS NULL
    BEGIN
        ALTER TABLE [analytics].[аналитика за 365 дн.]
        ADD [Продажи / квартал, руб.] decimal(38,2) NULL;
    END;

    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'учетная сумма / квартал, руб.') IS NULL
    BEGIN
        ALTER TABLE [analytics].[аналитика за 365 дн.]
        ADD [учетная сумма / квартал, руб.] decimal(38,2) NULL;
    END;

    IF COL_LENGTH('analytics.аналитика за 365 дн.', 'ТН / квартал, руб.') IS NULL
    BEGIN
        ALTER TABLE [analytics].[аналитика за 365 дн.]
        ADD [ТН / квартал, руб.] decimal(38,2) NULL;
    END;

    SET @start = DATEADD(month, -3, @end);

    ;WITH Q AS (
        SELECT
            v.[1c_id],
            SUM(v.[Сумма продаж])      AS sales_q,
            SUM(v.[Учетная сумма])     AS acct_q,
            SUM(v.[Торговая надбавка]) AS tn_q
        FROM [analytics].[Продажи_Учёт_Маржа_по_дням] v
        WHERE v.[d] >= @start AND v.[d] < @end
        GROUP BY v.[1c_id]
    )
    UPDATE a
    SET
        a.[Продажи / квартал, руб.]        = Q.sales_q,
        a.[учетная сумма / квартал, руб.]  = Q.acct_q,
        a.[ТН / квартал, руб.]             = Q.tn_q
    FROM [analytics].[аналитика за 365 дн.] AS a
    LEFT JOIN Q
      ON Q.[1c_id] = a.[1c_id];


    -------------------------------------------------------------
    -- 7) ТН / месяц (по текущей скорости)
    -------------------------------------------------------------
    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'ТН / месяц, руб.') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [ТН / месяц, руб.] decimal(38,6) NULL;
    END;

    UPDATE a
    SET a.[ТН / месяц, руб.] =
          ISNULL(a.[ТН / год, руб.], 0) 
          / NULLIF(a.[Продано шт], 0) 
          * ISNULL(a.[Продажи шт / день], 0) 
          * 30
    FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a;


    -------------------------------------------------------------------------------------
    -- 8) ДОПОЛНИТЕЛЬНО: Оплаченный остаток и рентабельности (ROIC и по остатку в руб.)
    --      (год назад, квартал назад и на будущий год) по 12-месячному окну
    -------------------------------------------------------------------------------------

    -- новые колонки
    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Оплаченный остаток') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Оплаченный остаток] decimal(38,2) NULL;
    END;

    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность / год') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Рентабельность / год] decimal(38,6) NULL;
    END;

    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность / квартал') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Рентабельность / квартал] decimal(38,6) NULL;
    END;

    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность / будущий год') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Рентабельность / будущий год] decimal(38,6) NULL;
    END;

    -- новые поля рентабельности по остатку в рублях (сразу в %)
    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность по остатку / год') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Рентабельность по остатку / год] decimal(38,6) NULL;
    END;

    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность по остатку / квартал') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Рентабельность по остатку / квартал] decimal(38,6) NULL;
    END;

    IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность по остатку / будущий год') IS NULL
    BEGIN
        ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
        ADD [Рентабельность по остатку / будущий год] decimal(38,6) NULL;
    END;

    ---------------------------------------------------------
    -- 8.1 Окно 12 полных месяцев
    ---------------------------------------------------------
    DECLARE @mp_today           date = CAST(GETDATE() AS date);
    DECLARE @mp_lastFullMonth   date = EOMONTH(DATEADD(month, -1, @mp_today));
    DECLARE @mp_firstMonthStart date = DATEADD(
                                         month, -11,
                                         DATEFROMPARTS(YEAR(@mp_lastFullMonth), MONTH(@mp_lastFullMonth), 1)
                                       );
    DECLARE @mp_curMonthStart   date = @mp_firstMonthStart;

    IF OBJECT_ID('tempdb..#mp_months') IS NOT NULL DROP TABLE #mp_months;
    CREATE TABLE #mp_months (
        MonthStart date NOT NULL PRIMARY KEY
    );

    WHILE @mp_curMonthStart <= DATEFROMPARTS(YEAR(@mp_lastFullMonth), MONTH(@mp_lastFullMonth), 1)
    BEGIN
        INSERT INTO #mp_months (MonthStart) VALUES (@mp_curMonthStart);
        SET @mp_curMonthStart = DATEADD(month, 1, @mp_curMonthStart);
    END;

    ---------------------------------------------------------
    -- 8.2 Набор SKU из аналитики 365
    ---------------------------------------------------------
    IF OBJECT_ID('tempdb..#mp_s') IS NOT NULL DROP TABLE #mp_s;
    CREATE TABLE #mp_s(
        [1c_id] binary(16) NOT NULL,
        code    nchar(11)  NOT NULL,
        CONSTRAINT PK_mp_s PRIMARY KEY ([1c_id], code)
    );

    INSERT INTO #mp_s([1c_id], code)
    SELECT DISTINCT a.[1c_id], a.[Code]
    FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a;

    ---------------------------------------------------------
    -- 8.3 Внешние остатки
    ---------------------------------------------------------
    IF OBJECT_ID('tempdb..#mp_ext_stock') IS NOT NULL DROP TABLE #mp_ext_stock;
    CREATE TABLE #mp_ext_stock(
        Dt   date          NOT NULL,
        code nchar(11)     NOT NULL,
        qty  numeric(18,3) NOT NULL,
        CONSTRAINT PK_mp_ext_stock PRIMARY KEY (Dt, code)
    );

    INSERT INTO #mp_ext_stock (Dt, code, qty)
    SELECT
          CAST(o.[Дата обновления] AS date) AS Dt,
          o.code,
          SUM(o.[Количество])              AS qty
    FROM analytics.[Внешние остатки] o
    JOIN #mp_s s
        ON s.code = o.code
    GROUP BY CAST(o.[Дата обновления] AS date), o.code;

    ---------------------------------------------------------
    -- 8.4 Приходы (Закупка / Приход)
    ---------------------------------------------------------
    IF OBJECT_ID('tempdb..#mp_incoming') IS NOT NULL DROP TABLE #mp_incoming;
    CREATE TABLE #mp_incoming(
        MonthStart date          NOT NULL,
        [1c_id]    binary(16)    NOT NULL,
        qty        numeric(18,3) NOT NULL,
        CONSTRAINT PK_mp_incoming PRIMARY KEY (MonthStart, [1c_id])
    );

    INSERT INTO #mp_incoming (MonthStart, [1c_id], qty)
    SELECT
          DATEFROMPARTS(YEAR(s.[Период]), MONTH(s.[Период]), 1) AS MonthStart,
          s.[1c_id],
          SUM(s.[Количество])                                   AS qty
    FROM pbiProd.[СводныйСебестоимость Для PBI] s
    JOIN #mp_s sf
        ON sf.[1c_id] = s.[1c_id]
    WHERE s.[Статья]       = N'Закупка'
      AND s.[Вид операции] = N'Приход'
      AND s.[Период]      >= @mp_firstMonthStart
    GROUP BY
          DATEFROMPARTS(YEAR(s.[Период]), MONTH(s.[Период]), 1),
          s.[1c_id];

    ---------------------------------------------------------
    -- 8.5 Обязательства по месяцам
    ---------------------------------------------------------
    IF OBJECT_ID('tempdb..#mp_obligations') IS NOT NULL DROP TABLE #mp_obligations;
    CREATE TABLE #mp_obligations(
        MonthStart date          NOT NULL,
        [1c_id]    binary(16)    NOT NULL,
        obligation numeric(18,2) NOT NULL,
        CONSTRAINT PK_mp_obligations PRIMARY KEY (MonthStart, [1c_id])
    );

    -- 8.5 Обязательства: считаем по manufacturer_payment_stage (поддержка 2, 3 и более этапов)
    -- Этап «оплачен», если конец месяца M >= дата прихода + days. Сумма этапа = full_sum * percent/100.
    -- Два шага через CTE, чтобы избежать Msg 8124 (correlated subquery с несколькими внешними ссылками).
    ;WITH base_oblig AS (
        SELECT
              m.MonthStart,
              inc.MonthStart AS inc_month,
              inc.[1c_id],
              inc.qty * n2.[Цена учетная, руб] AS full_sum,
              EOMONTH(inc.MonthStart) AS inc_eom,
              EOMONTH(m.MonthStart) AS eval_eom,
              man.id AS manufacturer_id
        FROM #mp_months m
        JOIN #mp_incoming inc ON 1 = 1
        JOIN pbi.nomenclature n2 ON n2.[1c_id] = inc.[1c_id]
        LEFT JOIN analytics.manufacturers man ON man.[manufacturer] = n2.Производитель
    ),
    oblig_with_paid AS (
        SELECT
              b.MonthStart,
			  b.eval_eom,
              b.inc_eom,
              b.[1c_id],
              b.full_sum,
              ISNULL(SUM(
                  CASE WHEN b.eval_eom >= DATEADD(day, st.[days], b.inc_eom)
                       THEN b.full_sum * st.[percent] / 100.0 ELSE 0 END
              ), 0) AS paid_to_month
        FROM base_oblig b
        LEFT JOIN analytics.manufacturer_payment_stage st ON st.manufacturer_id = b.manufacturer_id
        GROUP BY b.MonthStart, b.inc_month, b.[1c_id], b.full_sum, b.eval_eom, b.inc_eom, b.manufacturer_id
    ),
    ObligRows AS (
        SELECT
              MonthStart,
              [1c_id],
              CASE
                  WHEN eval_eom < inc_eom THEN 0
                  WHEN full_sum - paid_to_month > 0 THEN full_sum - paid_to_month
                  ELSE 0
              END AS ObligationPerIncoming
        FROM oblig_with_paid
    )
    INSERT INTO #mp_obligations (MonthStart, [1c_id], obligation)
    SELECT
          MonthStart,
          [1c_id],
          SUM(ObligationPerIncoming) AS obligation
    FROM ObligRows
    GROUP BY
          MonthStart,
          [1c_id];

    ---------------------------------------------------------
    -- 8.6 ТН по месяцам
    ---------------------------------------------------------
    IF OBJECT_ID('tempdb..#mp_tn_monthly') IS NOT NULL DROP TABLE #mp_tn_monthly;

    CREATE TABLE #mp_tn_monthly(
        MonthStart date          NOT NULL,
        [1c_id]    binary(16)    NOT NULL,
        tn_amount  numeric(18,2) NULL,
        CONSTRAINT PK_mp_tn_monthly PRIMARY KEY (MonthStart, [1c_id])
    );

    INSERT INTO #mp_tn_monthly (MonthStart, [1c_id], tn_amount)
    SELECT
          DATEFROMPARTS(YEAR(p.[d]), MONTH(p.[d]), 1) AS MonthStart,
          p.[1c_id],
          SUM(ISNULL(p.[Торговая надбавка],0))       AS tn_amount
    FROM [mag_pbi].[analytics].[Продажи_Учёт_Маржа_по_дням] p
    JOIN #mp_s s
        ON s.[1c_id] = p.[1c_id]
    WHERE p.[d] >= @mp_firstMonthStart
      AND p.[d] <= @mp_lastFullMonth
    GROUP BY
          DATEFROMPARTS(YEAR(p.[d]), MONTH(p.[d]), 1),
          p.[1c_id];

    ---------------------------------------------------------
    -- 8.7 Помесячный план по всем SKU (остаток, оплаченный остаток, ТН, рубли-дни, остаток учетка руб)
    ---------------------------------------------------------
    IF OBJECT_ID('tempdb..#mp_plan') IS NOT NULL DROP TABLE #mp_plan;

    SELECT
          s.[1c_id],
          s.code,
          m.MonthStart,
          [Остаток шт] =
              ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0),
          [Остаток учетка руб] =
              (ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0))
              * ISNULL(n.[Цена учетная, руб], 0),
          [Оплаченный остаток] =
              CAST(
                  CASE 
                      WHEN paid_raw < 0 THEN 0
                      ELSE paid_raw
                  END
                  AS decimal(18,2)
              ),
          [ТН] = ISNULL(tn.tn_amount, 0),
          [рубли-дни] =
              CAST(
                  CASE 
                      WHEN paid_raw < 0 THEN 0
                      ELSE paid_raw
                  END * DAY(EOMONTH(m.MonthStart))
                  AS decimal(18,2)
              )
    INTO #mp_plan
    FROM #mp_months m
    CROSS JOIN #mp_s s
    OUTER APPLY (
        SELECT TOP (1) w1.quantity
        FROM [mag_pbi].[pbi].[w_ostatok_da_net] w1
        WHERE w1._IDRREF = s.[1c_id]
          AND w1.dt <= EOMONTH(m.MonthStart)
        ORDER BY w1.dt DESC
    ) AS intStock
    LEFT JOIN #mp_ext_stock ext
        ON  ext.code = s.code
        AND ext.Dt   = EOMONTH(m.MonthStart)
    LEFT JOIN #mp_obligations ob
        ON  ob.[1c_id]    = s.[1c_id]
        AND ob.MonthStart = m.MonthStart
    LEFT JOIN [mag_pbi].[pbi].[nomenclature] n
        ON n.[1c_id] = s.[1c_id]
    LEFT JOIN #mp_tn_monthly tn
        ON  tn.[1c_id]    = s.[1c_id]
        AND tn.MonthStart = m.MonthStart
    CROSS APPLY (
        SELECT
              (ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0))
                  * ISNULL(n.[Цена учетная, руб], 0)
              - ISNULL(ob.obligation, 0) AS paid_raw
    ) p;

    ---------------------------------------------------------
    -- 8.8 Агрегаты по SKU: ROIC (год/квартал/будущий) + рентабельность по остатку (год/квартал/будущий)
    ---------------------------------------------------------

    -- старт квартала (последние 3 месяца окна)
    DECLARE @mp_q_start date = DATEADD(
        month, -2,
        DATEFROMPARTS(YEAR(@mp_lastFullMonth), MONTH(@mp_lastFullMonth), 1)
    );

    ;WITH AggYear AS (
        SELECT
            p.[1c_id],
            tn_year      = SUM(p.[ТН]),
            rd_year      = SUM(p.[рубли-дни]),
            avg_cap_year = AVG(NULLIF(p.[Остаток учетка руб], 0.0))
        FROM #mp_plan p
        GROUP BY p.[1c_id]
    ),
    AggQuarter AS (
        SELECT
            p.[1c_id],
            tn_q      = SUM(p.[ТН]),
            rd_q      = SUM(p.[рубли-дни]),
            avg_cap_q = AVG(NULLIF(p.[Остаток учетка руб], 0.0))
        FROM #mp_plan p
        WHERE p.MonthStart >= @mp_q_start
        GROUP BY p.[1c_id]
    ),
    RoicPast AS (
        SELECT
            ay.[1c_id],
            RoicValue =
                CASE 
                    WHEN ay.rd_year > 0
                    THEN ay.tn_year * 365.0 / ay.rd_year * 100.0
                    ELSE NULL
                END,
            ay.tn_year,
            ay.avg_cap_year
        FROM AggYear ay
    ),
    RoicQuarterPast AS (
        SELECT
            aq.[1c_id],
            RoicQ =
                CASE 
                    WHEN aq.rd_q > 0
                    THEN aq.tn_q * 365.0 / aq.rd_q * 100.0
                    ELSE NULL
                END,
            aq.tn_q,
            aq.avg_cap_q
        FROM AggQuarter aq
    ),
    LastRow AS (
        SELECT
            p.[1c_id],
            p.[Оплаченный остаток],
            p.[Остаток шт],
            p.[Остаток учетка руб],
            ROW_NUMBER() OVER (
                PARTITION BY p.[1c_id]
                ORDER BY p.MonthStart DESC
            ) AS rn
        FROM #mp_plan p
    ),
    FutureBase AS (
        SELECT
            a.[1c_id],
            a.[Продажи шт / день]      AS sales_per_day,
            a.[Продано шт]             AS sold_year,
            a.[ТН / год, руб.]         AS tn_year_hist,
            lr.[Оплаченный остаток]    AS paid_stock_now,
            lr.[Остаток шт]            AS q_stock,
            lr.[Остаток учетка руб]    AS stock_rub_now
        FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a
        LEFT JOIN LastRow lr
          ON lr.[1c_id] = a.[1c_id]
         AND lr.rn = 1
    ),
    FutureInputs AS (
        SELECT
            f.[1c_id],
            f.sales_per_day,
            f.sold_year,
            tn_year = COALESCE(f.tn_year_hist, rp.tn_year),
            f.paid_stock_now,
            f.q_stock,
            f.stock_rub_now,
            tn_per_unit =
                CASE 
                    WHEN f.sold_year > 0 AND COALESCE(f.tn_year_hist, rp.tn_year) IS NOT NULL
                    THEN COALESCE(f.tn_year_hist, rp.tn_year) / f.sold_year
                    ELSE NULL
                END,
            max_sell_1y =
                CASE 
                    WHEN f.sales_per_day IS NULL THEN NULL
                    ELSE f.sales_per_day * 365.0
                END
        FROM FutureBase f
        LEFT JOIN RoicPast rp
          ON rp.[1c_id] = f.[1c_id]
    ),
    FutureCalc AS (
        SELECT
            fi.[1c_id],
            RoicFuture =
              CASE 
                WHEN fi.paid_stock_now IS NULL OR fi.paid_stock_now <= 0
                 OR fi.sales_per_day IS NULL OR fi.sales_per_day <= 0
                 OR fi.tn_per_unit IS NULL
                 OR fi.q_stock IS NULL OR fi.q_stock <= 0
                 OR fi.max_sell_1y IS NULL OR fi.max_sell_1y <= 0
                THEN NULL
                ELSE
                (
                  (
                    fi.tn_per_unit *
                    (CASE 
                         WHEN fi.q_stock <= fi.max_sell_1y THEN fi.q_stock
                         ELSE fi.max_sell_1y
                     END)
                  ) * 365.0
                  /
                  (
                    CASE 
                      WHEN fi.q_stock / fi.sales_per_day >= 365.0
                           THEN fi.paid_stock_now * 365.0
                      ELSE fi.paid_stock_now * (fi.q_stock / fi.sales_per_day) / 2.0
                    END
                  ) * 100.0
                )
              END,
            tn_future =
              CASE 
                WHEN fi.tn_per_unit IS NULL OR fi.max_sell_1y IS NULL OR fi.q_stock IS NULL
                THEN NULL
                ELSE
                  fi.tn_per_unit *
                  (CASE 
                       WHEN fi.q_stock <= fi.max_sell_1y THEN fi.q_stock
                       ELSE fi.max_sell_1y
                   END)
              END,
            q_future =
              CASE 
                WHEN fi.max_sell_1y IS NULL OR fi.q_stock IS NULL THEN NULL
                WHEN fi.q_stock <= fi.max_sell_1y THEN fi.q_stock
                ELSE fi.max_sell_1y
              END
        FROM FutureInputs fi
    ),
    CapRentFuture AS (
        SELECT
           fi.[1c_id],
           RentCapFuturePct =
               CASE 
                 WHEN fc.tn_future IS NULL
                  OR fi.stock_rub_now IS NULL OR fi.stock_rub_now <= 0
                  OR fi.q_stock IS NULL OR fi.q_stock <= 0
                  OR fc.q_future IS NULL OR fc.q_future <= 0
                 THEN NULL
                 ELSE
                    CASE 
                       WHEN (fi.stock_rub_now * (1.0 - (fc.q_future / fi.q_stock) / 2.0)) <= 0
                       THEN NULL
                       ELSE
                          fc.tn_future 
                          / (fi.stock_rub_now * (1.0 - (fc.q_future / fi.q_stock) / 2.0))
                          * 100.0
                    END
               END
        FROM FutureInputs fi
        LEFT JOIN FutureCalc fc
          ON fc.[1c_id] = fi.[1c_id]
    )
    UPDATE a
    SET
        a.[Рентабельность / год]                   = rp.RoicValue,
        a.[Рентабельность / квартал]               = rq.RoicQ,
        a.[Оплаченный остаток]                     = lr.[Оплаченный остаток],
        a.[Рентабельность / будущий год]          = fc.RoicFuture,
        a.[Рентабельность по остатку / год]    =
            CASE 
                WHEN rp.avg_cap_year IS NOT NULL AND rp.avg_cap_year > 0
                     AND rp.tn_year IS NOT NULL
                THEN (rp.tn_year / rp.avg_cap_year) * 100.0
                ELSE NULL
            END,
        a.[Рентабельность по остатку / квартал] =
            CASE 
                WHEN rq.avg_cap_q IS NOT NULL AND rq.avg_cap_q > 0
                     AND rq.tn_q IS NOT NULL
                THEN (rq.tn_q / rq.avg_cap_q) * 100.0
                ELSE NULL
            END,
        a.[Рентабельность по остатку / будущий год] = cf.RentCapFuturePct
    FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a
    LEFT JOIN RoicPast        rp ON rp.[1c_id] = a.[1c_id]
    LEFT JOIN RoicQuarterPast rq ON rq.[1c_id] = a.[1c_id]
    LEFT JOIN LastRow         lr ON lr.[1c_id] = a.[1c_id] AND lr.rn = 1
    LEFT JOIN FutureCalc      fc ON fc.[1c_id] = a.[1c_id]
    LEFT JOIN CapRentFuture   cf ON cf.[1c_id] = a.[1c_id];


    -------------------------------------------------------------------------------------
    -- 9) Индекс (как был)
    -------------------------------------------------------------------------------------
    CREATE NONCLUSTERED INDEX [analyticsаналитика за 365 дн.]
    ON [analytics].[аналитика за 365 дн.] ([1c_id])
    INCLUDE (
        [Продажи шт / день],
        [Остаток дней продаж],
        [ТН / год, руб.],
        [ТН / квартал, руб.],
        [Рентабельность / год],
        [Рентабельность / квартал],
        [Дней в продаже / год],
        [Дней в продаже / квартал]
    );
END
GO

/****** Object:  StoredProcedure [analytics].[sp_fill_deficit_money_request]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE   PROCEDURE [analytics].[sp_fill_deficit_money_request]
    @scenario_id INT = NULL  -- NULL = MAX(scenario_id) из deficit_proposal
AS
SET NOCOUNT ON;

DECLARE @scenario INT;

IF @scenario_id IS NOT NULL AND @scenario_id > 0
    SET @scenario = @scenario_id;
ELSE
    SET @scenario = (SELECT MAX(scenario_id) FROM [analytics].[deficit_proposal]);

IF @scenario IS NULL
BEGIN
    RAISERROR(N'Нет данных в deficit_proposal. Выполните sp_build_deficit_proposal.', 16, 1);
    RETURN;
END

-- Очищаем таблицу для данного сценария
DELETE FROM [analytics].[deficit_money_request] WHERE scenario_id = @scenario;

-- Заполняем: дефицит -> номенклатура (производитель, цена) -> manufacturers -> контрагент
-- Номенклатура: pbi.v_nomenclature_full содержит все коды, Производитель, учётную цену
INSERT INTO [analytics].[deficit_money_request] (
    scenario_id,
    manufacturer_id,
    contractor_1c_id,
    manufacturer_name,
    contractor_name,
    arrival_month,
    amount_rub,
    amount_usd,
    currency,
    order_qty_total,
    sku_count,
    updated_at
)
SELECT
    @scenario AS scenario_id,
    m.id AS manufacturer_id,
    mcm.contractor_1c_id,
    m.manufacturer AS manufacturer_name,
    c.contractor_name,
    CAST(FORMAT(dp.arrival_month, 'yyyy-MM-01') AS date) AS arrival_month,
    SUM(dp.order_qty * ISNULL(n.[Цена учетная, руб], 0)) AS amount_rub,
    SUM(dp.order_qty * ISNULL(n.[Цена учетная, usd], 0)) AS amount_usd,
    CASE 
        WHEN SUM(dp.order_qty * ISNULL(n.[Цена учетная, usd], 0)) > 
             SUM(dp.order_qty * ISNULL(n.[Цена учетная, руб], 0)) * 0.01 
        THEN N'USD' ELSE N'руб.' 
    END AS currency,
    SUM(dp.order_qty) AS order_qty_total,
    COUNT(DISTINCT dp.code) AS sku_count,
    SYSDATETIME() AS updated_at
FROM [analytics].[deficit_proposal] dp
-- Номенклатура: все коды, производитель и учётная цена (без фильтра по группе)
INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n 
    ON n.[code] = dp.code
-- Производитель: сопоставление по имени (LTRIM/RTRIM как в contractorProducer)
INNER JOIN [analytics].[manufacturers] m 
    ON LTRIM(RTRIM(ISNULL(n.[Производитель], N''))) = LTRIM(RTRIM(m.manufacturer))
    AND LTRIM(RTRIM(ISNULL(n.[Производитель], N''))) <> N''
LEFT JOIN [analytics].[manufacturer_counterparty_map] mcm 
    ON mcm.manufacturer_id = m.id
LEFT JOIN [analytics].[v_contractors] c 
    ON c.contractor_1c_id = mcm.contractor_1c_id
WHERE dp.scenario_id = @scenario
  AND dp.order_qty > 0
  AND (n.[Цена учетная, руб] IS NOT NULL OR n.[Цена учетная, usd] IS NOT NULL)
GROUP BY
    m.id,
    mcm.contractor_1c_id,
    m.manufacturer,
    c.contractor_name,
    CAST(FORMAT(dp.arrival_month, 'yyyy-MM-01') AS date);

-- Логируем результат
DECLARE @rows INT = @@ROWCOUNT;
PRINT CONCAT(N'analytics.sp_fill_deficit_money_request: внесено ', @rows, N' записей для scenario_id=', @scenario);
GO

/****** Object:  StoredProcedure [analytics].[sp_load_koef_groups]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [analytics].[sp_load_koef_groups] as BEGIN

Print('Запускайте код процедуры руками')
RETURN 0

DROP TABLE #koef

CREATE TABLE #koef (
    [group_1c_id] nvarchar(256) NOT NULL,
	[seasonal_koef] [decimal](18, 6) NOT NULL,
	[month] int NOT NULL
);

BULK INSERT #koef
FROM '\\192.168.35.3\admin3\Обмен\powerbi\k1.csv'
WITH (
    FIRSTROW = 2,                -- пропустить заголовок
    FIELDTERMINATOR = ';',
    ROWTERMINATOR = '\n',
    CODEPAGE = '1251'            -- если кириллица
);


-- Проверить наличие данных во вр таблице
select top 100 * from #koef


--DELETE FROM [analytics].[seasonality_groups]
INSERT INTO [analytics].[seasonality_groups]
SELECT 
	g.[1c_id],
	k.month,
	k.seasonal_koef
FROM #koef k
INNER JOIN pbi.groups g
	ON g.[1c_id]=k.group_1c_id



	------------------------------
	SET NOCOUNT ON;

------------------------------------------------------------
-- 1. Таблица месяцев 1..12
------------------------------------------------------------
IF OBJECT_ID('tempdb..#Months') IS NOT NULL DROP TABLE #Months;

CREATE TABLE #Months (
    [month] tinyint NOT NULL PRIMARY KEY
);

INSERT INTO #Months ([month])
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12);


------------------------------------------------------------
-- 2. База продаж 2024–2025 по g–g1 × месяц
------------------------------------------------------------
IF OBJECT_ID('tempdb..#SalesByGroupMonth') IS NOT NULL DROP TABLE #SalesByGroupMonth;

SELECT
    g.[g],
    g.[g1],
    [month] = MONTH(s.[Период]),
    qty     = /* SUM(s.Сумма) */ SUM(s.[Количество])
INTO #SalesByGroupMonth
FROM [mag_pbi].[pbiProd].[СводныйСебестоимость Для PBI] AS s
    INNER JOIN [pbi].[nomenclature] n
        ON n.[1c_id] = s.[1c_id]
    INNER JOIN [pbi].[groups] g
        ON g.[1c_id] = n.[1c_group]
WHERE
    s.[Статья] = N'Реализация'
    AND s.[Период] >= '2023-12-01'
    AND s.[Период] <  '2025-12-01'
    AND g.[g] NOT LIKE N'*%'
GROUP BY
    g.[g],
    g.[g1],
    MONTH(s.[Период]);


------------------------------------------------------------
-- 3. Список всех g–g1, которые продавались
------------------------------------------------------------
IF OBJECT_ID('tempdb..#GroupList') IS NOT NULL DROP TABLE #GroupList;

SELECT DISTINCT
    sbgm.[g],
    sbgm.[g1]
INTO #GroupList
FROM #SalesByGroupMonth sbgm;


------------------------------------------------------------
-- 4. g–g1 × все 12 месяцев (qty = 0, если продаж не было)
------------------------------------------------------------
IF OBJECT_ID('tempdb..#AllGroupMonths') IS NOT NULL DROP TABLE #AllGroupMonths;

SELECT
    gl.[g],
    gl.[g1],
    m.[month],
    qty = ISNULL(sbgm.qty, 0)
INTO #AllGroupMonths
FROM #GroupList gl
CROSS JOIN #Months m
LEFT JOIN #SalesByGroupMonth sbgm
    ON  sbgm.[g]     = gl.[g]
    AND sbgm.[g1]    = gl.[g1]
    AND sbgm.[month] = m.[month];


------------------------------------------------------------
-- 5. Общий объём продаж по g–g1 за все 12 месяцев
------------------------------------------------------------
IF OBJECT_ID('tempdb..#GroupTotals') IS NOT NULL DROP TABLE #GroupTotals;

SELECT
    agm.[g],
    agm.[g1],
    total_qty = SUM(agm.qty)
INTO #GroupTotals
FROM #AllGroupMonths agm
GROUP BY
    agm.[g],
    agm.[g1];


------------------------------------------------------------
-- 6. Финальный результат: g, g1, koef, month
--    Сумма koef по 12 месяцам для каждого g–g1 = 1
------------------------------------------------------------
SELECT
    agm.[g],
    agm.[g1],
    agm.[month],
    koef = CASE 
               WHEN gt.total_qty = 0 THEN 0
               ELSE CAST(agm.qty AS decimal(18,6)) / NULLIF(gt.total_qty, 0)
           END
--INTO analytics .seasonality_groups_summ_1
FROM #AllGroupMonths agm
INNER JOIN #GroupTotals gt
    ON  gt.[g]  = agm.[g]
    AND gt.[g1] = agm.[g1]
ORDER BY
    agm.[g],
    agm.[g1],
    agm.[month];

END
GO

/****** Object:  StoredProcedure [analytics].[sp_rebuild_stock_plan_by_arrival]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE   PROCEDURE [analytics].[sp_rebuild_stock_plan_by_arrival]
    @scenario_id   INT,
    @from_month    DATE,   -- например: DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)
    @to_month      DATE    -- последний месяц горизонта + 1 день (интервал [from, to) по месяцам)
AS
BEGIN
    SET NOCOUNT ON;

    -- Безопасная рамка
    IF @from_month IS NULL SET @from_month = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
    IF @to_month   IS NULL SET @to_month   = DATEADD(MONTH, 12, @from_month); -- 12 мес по умолчанию

    -- Чистим целевой диапазон
    DELETE FROM [analytics].[stock_plan_by_arrival]
      WHERE [scenario_id] = @scenario_id
        AND [arrival_month] >= @from_month
        AND [arrival_month] <  @to_month;

    ;WITH months AS (   -- календарь месяцев [from, to)
        SELECT CAST(@from_month AS DATE) AS m
        UNION ALL
        SELECT DATEADD(MONTH, 1, m) FROM months WHERE DATEADD(MONTH, 1, m) < @to_month
    ),
    -- 1) Стартовый остаток на начало горизонта (берём последний известный quantity)
    opening AS (
        SELECT w._IDRREF               AS [1c_id],
               n.code                  AS [code],
               CAST(@from_month AS DATE) AS [arrival_month],
               CAST(
                   MAX(CASE WHEN w.dt = x.max_dt THEN w.quantity END)
                 AS DECIMAL(18,3))     AS opening_qty
        FROM [pbi].[w_ostatok_da_net] w
        JOIN (
            SELECT _IDRREF, MAX(dt) AS max_dt
            FROM [pbi].[w_ostatok_da_net]
            WHERE dt < @from_month
            GROUP BY _IDRREF
        ) x ON x._IDRREF = w._IDRREF AND x.max_dt = w.dt
        JOIN [pbi].[nomenclature] n ON n.[1c_id] = w._IDRREF
        GROUP BY w._IDRREF, n.code
    ),
    -- 2) Подтвержденные приходы (ожидаемые заказы) по дате прихода → к первому дню месяца
    inbound_confirmed AS (
        SELECT 
            o.[1c_id],
            n.code,
            o.month AS arrival_month,
            CAST(SUM(o.units) AS DECIMAL(18,3)) AS inbound_confirmed
        FROM [mag_pbi].[analytics].[get_orders_by_group] o
        JOIN [pbi].[nomenclature] n ON n.[1c_id] = o.[1c_id]
        WHERE o.[status] IN (N'В пути', N'В производстве', N'Выгружен на складе', N'Согласован')
          AND o.month >= @from_month
          AND o.month <  @to_month
        GROUP BY o.[1c_id], n.code,
                 o.month
    ),
    -- 3) Будущие заказы (дефицит → рекомендованные поставки) по месяцу прихода
    inbound_deficit AS (
        SELECT 
            d.[1c_id],
            d.[code],
            DATEFROMPARTS(YEAR(d.[arrival_month]), MONTH(d.[arrival_month]), 1) AS arrival_month,
            CAST(SUM(d.[order_qty]) AS DECIMAL(18,3)) AS inbound_deficit
        FROM [analytics].[deficit_proposal] d
        WHERE d.[scenario_id] = @scenario_id
          AND d.[arrival_month] >= @from_month
          AND d.[arrival_month] <  @to_month
        GROUP BY d.[1c_id], d.[code],
                 DATEFROMPARTS(YEAR(d.[arrival_month]), MONTH(d.[arrival_month]), 1)
    ),
    -- 4) Прогноз спроса по месяцам
    forecast AS (
        SELECT 
            f.[1c_id],
            f.[code],
            DATEFROMPARTS(YEAR(f.[month]), MONTH(f.[month]), 1) AS arrival_month,
            CAST(SUM(f.[value]) AS DECIMAL(18,3)) AS forecast_demand
        FROM [analytics].[forecast] f
        WHERE f.[scenario_id] = @scenario_id
          AND f.[month] >= @from_month
          AND f.[month] <  @to_month
        GROUP BY f.[1c_id], f.[code],
                 DATEFROMPARTS(YEAR(f.[month]), MONTH(f.[month]), 1)
    ),
    -- 5) Объединим каркас всех SKU × месяцы горизонта
    sku_calendar AS (
        SELECT DISTINCT n.[1c_id], n.[code], m.m AS arrival_month
        FROM [pbi].[nomenclature] n
        CROSS JOIN months m
    ),
    base_union AS (
        SELECT c.[1c_id], c.[code], c.[arrival_month],
               COALESCE(op.opening_qty, 0)                       AS opening_qty,
               COALESCE(ic.inbound_confirmed, 0)                 AS inbound_confirmed,
               COALESCE(idf.inbound_deficit, 0)                  AS inbound_deficit,
               COALESCE(fc.forecast_demand, 0)                   AS forecast_demand
        FROM sku_calendar c
        LEFT JOIN opening           op  ON op.[1c_id]=c.[1c_id] AND op.[code]=c.[code] AND op.[arrival_month]=@from_month
        LEFT JOIN inbound_confirmed ic  ON ic.[1c_id]=c.[1c_id] AND ic.[code]=c.[code] AND ic.[arrival_month]=c.[arrival_month]
        LEFT JOIN inbound_deficit   idf ON idf.[1c_id]=c.[1c_id] AND idf.[code]=c.[code] AND idf.[arrival_month]=c.[arrival_month]
        LEFT JOIN forecast          fc  ON fc.[1c_id]=c.[1c_id] AND fc.[code]=c.[code] AND fc.[arrival_month]=c.[arrival_month]
        WHERE c.[arrival_month] >= @from_month
          AND c.[arrival_month] <  @to_month
    ),
    -- 6) Расчёт rolling opening/closing по месяцам
    projected AS (
        SELECT
            @scenario_id         AS scenario_id,
            b.[1c_id],
            b.[code],
            b.[arrival_month],
            -- opening: для первого месяца берем opening_qty из "opening", далее — прошлый closing
            CAST(
                CASE 
                    WHEN b.[arrival_month] = @from_month 
                         THEN b.[opening_qty]
                    ELSE 0
                END
            AS DECIMAL(18,3)) AS opening_qty,
            b.[inbound_confirmed],
            b.[inbound_deficit],
            b.[forecast_demand]
        FROM base_union b
    )
    -- Вставка построчно с расчетом closing через окно
    INSERT INTO [analytics].[stock_plan_by_arrival]
        ([scenario_id],[arrival_month],[1c_id],[code],
         [opening_qty],[inbound_confirmed],[inbound_deficit],[forecast_demand],[closing_qty],[updated_at])
    SELECT
        p.scenario_id,
        p.arrival_month,
        p.[1c_id],
        p.[code],
        -- opening по месяцу = LAG(closing) OVER (по SKU упорядочено по месяцу), для первого — opening_qty
        CAST(
            COALESCE(
                LAG( closing_calc ) OVER (PARTITION BY p.[1c_id], p.[code] ORDER BY p.arrival_month),
                p.opening_qty
            )
        AS DECIMAL(18,3)) AS opening_qty,
        p.inbound_confirmed,
        p.inbound_deficit,
        p.forecast_demand,
        -- closing = opening + inbound_confirmed + inbound_deficit - forecast
        CAST(
            (
                COALESCE( LAG( closing_calc ) OVER (PARTITION BY p.[1c_id], p.[code] ORDER BY p.arrival_month), p.opening_qty)
                + p.inbound_confirmed
                + p.inbound_deficit
                - p.forecast_demand
            ) AS DECIMAL(18,3)
        ) AS closing_qty,
        SYSUTCDATETIME()
    FROM (
        -- промежуточное вычисление closing для использования в LAG
        SELECT
            scenario_id, [1c_id], [code], arrival_month, opening_qty, inbound_confirmed, inbound_deficit, forecast_demand,
            CAST(opening_qty + inbound_confirmed + inbound_deficit - forecast_demand AS DECIMAL(18,3)) AS closing_calc
        FROM projected
    ) p
    OPTION (MAXRECURSION 0);

END
GO

/****** Object:  StoredProcedure [analytics].[sp_recalc_roic]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [analytics].[sp_recalc_roic]
    @manufacturer_id INT = NULL
AS
SET NOCOUNT ON;

;WITH stage_agg AS (
    SELECT
        manufacturer_id,
        SUM([percent] / 100.0 * [days]) AS effective_deferral_days,
        SUM([percent] / 100.0) AS total_percent
    FROM [analytics].[manufacturer_payment_stage]
    WHERE @manufacturer_id IS NULL OR manufacturer_id = @manufacturer_id
    GROUP BY manufacturer_id
),
calc AS (
    SELECT
        man.id,
        man.days_of_sales,
        man.logistics_days,
        COALESCE(s.effective_deferral_days, 0) AS effective_deferral_days,
        COALESCE(s.total_percent, 0) AS total_percent,
        (ISNULL(man.logistics_days, 120) + ISNULL(man.days_of_sales, 180) / 2.0) AS avg_return_day
    FROM [analytics].[manufacturers] man
    LEFT JOIN stage_agg s ON s.manufacturer_id = man.id
    WHERE @manufacturer_id IS NULL OR man.id = @manufacturer_id
),
roic_calc AS (
    SELECT
        id,
        CASE
            WHEN total_percent <= 0 THEN NULL
            WHEN (avg_return_day - effective_deferral_days) <= 0 THEN NULL
            ELSE ROUND(12.0 / ((avg_return_day - effective_deferral_days) / 30.0) * 100.0, 2)
        END AS new_roic
    FROM calc
)
UPDATE man
SET man.roic_norm = r.new_roic
FROM [analytics].[manufacturers] man
JOIN roic_calc r ON r.id = man.id;

SELECT @@ROWCOUNT AS updated_count;
GO

/****** Object:  StoredProcedure [analytics].[sp_report_ROI]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE   PROCEDURE [analytics].[sp_report_ROI]
    @scenario_id      INT             = 4,
    @min_deficit_rub  DECIMAL(18,2)   = 2000,
    @cutoff_month     DATE            = '2027-01-01',
    @pathPattern      NVARCHAR(400)    = N'Кружево%'
AS
BEGIN
    SET NOCOUNT ON;

    ;WITH stages_pivot AS (
        SELECT manufacturer_id,
            MAX(CASE WHEN rn = 1 THEN [percent] END) / 100.0 AS n_percent,
            MAX(CASE WHEN rn = 1 THEN days END) AS n_days,
            MAX(CASE WHEN rn = 2 THEN [percent] END) / 100.0 AS m_percent,
            MAX(CASE WHEN rn = 2 THEN days END) AS m_days
        FROM (
            SELECT manufacturer_id, [percent], days,
                ROW_NUMBER() OVER (PARTITION BY manufacturer_id ORDER BY sort_order) AS rn
            FROM [analytics].[manufacturer_payment_stage]
        ) t WHERE rn <= 2
        GROUP BY manufacturer_id
    ),
    base AS (
        SELECT
              g.[path] AS grp
            , n.[Производитель]
            , d.place_month
            , SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)) AS Deficit
            , SUM(a.[%ТН год, руб.] * d.order_qty * ISNULL(n.[Цена учетная, руб], 0))
                / NULLIF(SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)), 0) AS [%ТН средн]
        FROM [analytics].[deficit_proposal] d
        INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = d.[1c_id]
        INNER JOIN [analytics].[аналитика за 365 дн.] a ON a.[1c_id] = d.[1c_id]
        INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
        WHERE d.scenario_id = @scenario_id
          AND ISNULL(n.[cenovaya_gruppa], N'') = N'Валютная'
          AND g.[path] LIKE @pathPattern
        GROUP BY g.[path], n.[Производитель], d.place_month
        HAVING SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)) > @min_deficit_rub
    )
    SELECT
          b.grp AS [g]
        , b.[Производитель]
        , b.place_month
        , b.Deficit
        , b.[%ТН средн]
        , b.[%ТН средн] * ISNULL(m.roic_norm, 1.36) AS [%ROI заказа]
        , ISNULL(m.roic_norm, 1.36) AS [ROI normalized]
        , b.[%ТН средн] * ISNULL(m.roic_norm, 1.36) * b.Deficit AS [ROI руб]
        , ISNULL(sp.n_percent, 0.30) AS n_percent
        , ISNULL(sp.n_days, 1) AS n_days
        , ISNULL(sp.m_percent, 0.70) AS m_percent
        , ISNULL(sp.m_days, 60) AS m_days
    FROM base b
    LEFT JOIN [analytics].[manufacturers] m
        ON LTRIM(RTRIM(ISNULL(b.[Производитель], N''))) = LTRIM(RTRIM(m.manufacturer))
        AND LTRIM(RTRIM(ISNULL(b.[Производитель], N''))) <> N''
    LEFT JOIN stages_pivot sp ON sp.manufacturer_id = m.id
    ORDER BY [%ROI заказа] DESC, b.grp, b.Deficit DESC;

    -- 2. Платежи по месяцам (из deficit_proposal по path — dmr не хранит path)
    SELECT
          FORMAT(d.arrival_month, 'yyyy-MM') AS [Дата платежа]
        , SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)) AS [Сумма платежа]
    FROM [analytics].[deficit_proposal] d
    INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = d.[1c_id]
    INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
    WHERE d.scenario_id = @scenario_id
      AND g.[path] LIKE @pathPattern
    GROUP BY FORMAT(d.arrival_month, 'yyyy-MM')
    ORDER BY FORMAT(d.arrival_month, 'yyyy-MM') ASC;

    -- 3. Платежи до cutoff (из deficit_proposal по path)
    SELECT
        SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)) AS [Платежи в 2026]
    FROM [analytics].[deficit_proposal] d
    INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = d.[1c_id]
    INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
    WHERE d.scenario_id = @scenario_id
      AND g.[path] LIKE @pathPattern
      AND d.arrival_month < @cutoff_month;

    -- 4. Прогноз выручки по месяцам
    SELECT
          t.[month]
        , SUM(t.Выручка) AS revenue
        , SUM(t.[Сумм. учет]) AS uchet
    FROM (
        SELECT
              f.[month]
            , f.[value] * ISNULL(n.[Цена учетная, руб], 0) AS [Сумм. учет]
            , f.[value] * ISNULL(n.[Цена учетная, руб], 0) * (1 + a.[%ТН год, руб.]) AS Выручка
        FROM [analytics].[forecast] f
        INNER JOIN [analytics].[аналитика за 365 дн.] a ON a.[1c_id] = f.[1c_id]
        INNER JOIN [mag_pbi].[pbi].[nomenclature] n ON n.[1c_id] = f.[1c_id]
        INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
        WHERE f.scenario_id = @scenario_id
          AND a.[%ТН год, руб.] > 0
          AND g.[path] LIKE @pathPattern
    ) t
    GROUP BY t.[month]
    ORDER BY t.[month];

    -- 5. ROIC по группе (path pattern)
    ;WITH sku_in_group AS (
        SELECT
              a.[1c_id]
            , a.[Code] AS code
            , g.[path]
            , a.[Оплаченный остаток] AS PaidCapital
            , a.[Рентабельность по остатку / год] AS RoicPast
        FROM [analytics].[аналитика за 365 дн.] a
        INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = a.[1c_id]
        INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
        WHERE g.[path] LIKE @pathPattern
    )
    SELECT
          @pathPattern AS [Фильтр path]
        , SUM(PaidCapital) AS [Оплаченный остаток всего, руб]
        , CASE
              WHEN SUM(PaidCapital) > 0
              THEN SUM(ISNULL(PaidCapital, 0) * ISNULL(RoicPast, 0)) / SUM(PaidCapital)
              ELSE NULL
          END AS [Рентабельность остатка / год назад, %]
    FROM sku_in_group;

    -- 6. Итоговые ROIC по всей аналитике
    DECLARE @sumPaid           DECIMAL(38,6);
    DECLARE @sumWeightedPast   DECIMAL(38,6);
    DECLARE @sumWeightedFuture DECIMAL(38,6);
    DECLARE @roicPastTotal     DECIMAL(38,6);
    DECLARE @roicFutureTotal   DECIMAL(38,6);

    SELECT
          @sumPaid = SUM(CAST(ISNULL(a.[Оплаченный остаток], 0) AS DECIMAL(38,6)))
        , @sumWeightedPast = SUM(
              CAST(ISNULL(a.[Оплаченный остаток], 0) AS DECIMAL(38,6))
            * CAST(ISNULL(a.[Рентабельность по остатку / год], 0) AS DECIMAL(38,6))
          )
        , @sumWeightedFuture = SUM(
              CAST(ISNULL(a.[Оплаченный остаток], 0) AS DECIMAL(38,6))
            * CAST(ISNULL(a.[Рентабельность / будущий год], 0) AS DECIMAL(38,6))
          )
    FROM [analytics].[аналитика за 365 дн.] a
    INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = a.[1c_id]
    INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
    WHERE g.[path] LIKE @pathPattern;

    SET @roicPastTotal   = CASE WHEN @sumPaid > 0 THEN @sumWeightedPast / @sumPaid ELSE NULL END;
    SET @roicFutureTotal = CASE WHEN @sumPaid > 0 THEN @sumWeightedFuture / @sumPaid ELSE NULL END;

    SELECT
          @sumPaid         AS [Оплаченный остаток всего, руб]
        , @roicPastTotal   AS [Рентабельность остатка / год назад всего, %]
        , @roicFutureTotal AS [Рентабельность остатка / будущий год всего, %];
END
GO

/****** Object:  StoredProcedure [analytics].[sp_report_ROI_подробно]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure  [analytics].[sp_report_ROI_подробно] as BEGIN
---------------------------------------------------------
-- 0. Параметры дат: 12 полных месяцев НАЗАД
---------------------------------------------------------
DECLARE @today           date = CAST(GETDATE() AS date);
DECLARE @lastFullMonth   date;
DECLARE @firstMonthStart date;
DECLARE @curMonthStart   date;

-- последний полный месяц (месяц до текущего дня)
SET @lastFullMonth   = EOMONTH(DATEADD(month, -1, @today));
-- первый из 12 полных месяцев
SET @firstMonthStart = DATEADD(
                           month, -11,
                           DATEFROMPARTS(YEAR(@lastFullMonth), MONTH(@lastFullMonth), 1)
                       );
SET @curMonthStart   = @firstMonthStart;

---------------------------------------------------------
-- 1. Список месяцев (#months)
---------------------------------------------------------
IF OBJECT_ID('tempdb..#months') IS NOT NULL DROP TABLE #months;

CREATE TABLE #months (
    MonthStart date NOT NULL PRIMARY KEY
);

WHILE @curMonthStart
      <= DATEFROMPARTS(YEAR(@lastFullMonth), MONTH(@lastFullMonth), 1)
BEGIN
    INSERT INTO #months (MonthStart)
    VALUES (@curMonthStart);

    SET @curMonthStart = DATEADD(month, 1, @curMonthStart);
END;

---------------------------------------------------------
-- 2. SKU (#s) – здесь укажи нужный код товара
---------------------------------------------------------
IF OBJECT_ID('tempdb..#s') IS NOT NULL DROP TABLE #s;

CREATE TABLE #s (
    [1c_id] binary(16) NOT NULL,
    code    nchar(11)  NOT NULL
);

INSERT INTO #s ([1c_id], code)
SELECT n.[1c_id], n.code
FROM pbi.nomenclature n
WHERE n.code = N'УТ-00176242';   -- <=== поменяй при необходимости

---------------------------------------------------------
-- 3. Внешние остатки (#ext_stock) – срезы на конец месяца
---------------------------------------------------------
IF OBJECT_ID('tempdb..#ext_stock') IS NOT NULL DROP TABLE #ext_stock;

CREATE TABLE #ext_stock (
    Dt   date          NOT NULL,
    code nchar(11)     NOT NULL,
    qty  numeric(18,3) NOT NULL,
    CONSTRAINT PK_ext_stock PRIMARY KEY (Dt, code)
);

INSERT INTO #ext_stock (Dt, code, qty)
SELECT
      CAST(o.[Дата обновления] AS date) AS Dt,
      o.code,
      SUM(o.[Количество])              AS qty
FROM analytics.[Внешние остатки] o
JOIN #s s
    ON s.code = o.code
GROUP BY CAST(o.[Дата обновления] AS date), o.code;

---------------------------------------------------------
-- 4. Приходы (#incoming) – Закупка / Приход по месяцам
--    БЕРЁМ ТОЛЬКО ПРИХОДЫ ВНУТРИ НАШИХ 12 МЕСЯЦЕВ
---------------------------------------------------------
IF OBJECT_ID('tempdb..#incoming') IS NOT NULL DROP TABLE #incoming;

CREATE TABLE #incoming (
    MonthStart date          NOT NULL,  -- месяц прихода (1-е число)
    code       nchar(11)     NOT NULL,
    qty        numeric(18,3) NOT NULL,
    CONSTRAINT PK_incoming PRIMARY KEY (MonthStart, code)
);

INSERT INTO #incoming (MonthStart, code, qty)
SELECT
      DATEFROMPARTS(YEAR(s.[Период]), MONTH(s.[Период]), 1) AS MonthStart,
      n.code,
      SUM(s.[Количество])                                   AS qty
FROM pbiProd.[СводныйСебестоимость Для PBI] s
JOIN pbi.nomenclature n
    ON n.[1c_id] = s.[1c_id]
JOIN #s sfilter
    ON sfilter.code = n.code
WHERE s.[Статья]       = N'Закупка'
  AND s.[Вид операции] = N'Приход'
  AND s.[Период]      >= @firstMonthStart          -- отключаем старые приходы
GROUP BY
      DATEFROMPARTS(YEAR(s.[Период]), MONTH(s.[Период]), 1),
      n.code;

---------------------------------------------------------
-- 5. Части платежей по каждому приходу (#pay_parts)
--    (1-й и 2-й платёж по производителю, проценты как ДОЛИ)
---------------------------------------------------------
IF OBJECT_ID('tempdb..#pay_parts') IS NOT NULL DROP TABLE #pay_parts;

CREATE TABLE #pay_parts (
    MonthStart date          NOT NULL,  -- месяц прихода (ключ к #incoming)
    code       nchar(11)     NOT NULL,
    pay_date   date          NOT NULL,  -- дата платежа
    amount     numeric(18,2) NOT NULL
);

-- первый платёж (n)
INSERT INTO #pay_parts (MonthStart, code, pay_date, amount)
SELECT
      inc.MonthStart,
      inc.code,
      DATEADD(day, man.n_days - 90, EOMONTH(inc.MonthStart)) AS pay_date,
      inc.qty * n.[Цена учетная, руб] * man.n_percent        AS amount
FROM #incoming inc
JOIN pbi.nomenclature n
    ON n.code = inc.code
JOIN analytics.manufacturers man
    ON man.[manufacturer] = n.Производитель
WHERE man.n_percent IS NOT NULL
  AND man.n_percent <> 0;

-- второй платёж (m)
INSERT INTO #pay_parts (MonthStart, code, pay_date, amount)
SELECT
      inc.MonthStart,
      inc.code,
      DATEADD(day, man.m_days - 90, EOMONTH(inc.MonthStart)) AS pay_date,
      inc.qty * n.[Цена учетная, руб] * man.m_percent        AS amount
FROM #incoming inc
JOIN pbi.nomenclature n
    ON n.code = inc.code
JOIN analytics.manufacturers man
    ON man.[manufacturer] = n.Производитель
WHERE man.m_percent IS NOT NULL
  AND man.m_percent <> 0;

---------------------------------------------------------
-- 6. Платежи по месяцам (#payments) – для колонки [платеж]
---------------------------------------------------------
IF OBJECT_ID('tempdb..#payments') IS NOT NULL DROP TABLE #payments;

CREATE TABLE #payments (
    MonthStart date          NOT NULL,  -- месяц платежа (1-е число)
    code       nchar(11)     NOT NULL,
    amount     numeric(18,2) NOT NULL,
    CONSTRAINT PK_payments PRIMARY KEY (MonthStart, code)
);

INSERT INTO #payments (MonthStart, code, amount)
SELECT
      DATEFROMPARTS(YEAR(p.pay_date), MONTH(p.pay_date), 1) AS MonthStart,
      p.code,
      SUM(p.amount)                                         AS amount
FROM #pay_parts p
GROUP BY
      DATEFROMPARTS(YEAR(p.pay_date), MONTH(p.pay_date), 1),
      p.code;

---------------------------------------------------------
-- 7. Обязательства по месяцам (#obligations)
--    Считаем только по приходам (#incoming)
---------------------------------------------------------
IF OBJECT_ID('tempdb..#obligations') IS NOT NULL DROP TABLE #obligations;

CREATE TABLE #obligations (
    MonthStart date          NOT NULL,  -- месяц, на который считаем долг
    code       nchar(11)     NOT NULL,
    obligation numeric(18,2) NOT NULL,
    CONSTRAINT PK_obligations PRIMARY KEY (MonthStart, code)
);

;WITH ObligRows AS (
    SELECT
          m.MonthStart,
          inc.code,
          CASE
              -- если месяц ещё до прихода товара – долга нет
              WHEN EOMONTH(m.MonthStart) < EOMONTH(inc.MonthStart) THEN 0
              ELSE
                  CASE 
                      WHEN full_sum - paid_to_month > 0 
                      THEN full_sum - paid_to_month
                      ELSE 0
                  END
          END AS ObligationPerIncoming
    FROM #months m
    JOIN #incoming inc
        ON 1 = 1                               -- каждая пара (месяц, приход)
    JOIN pbi.nomenclature n2
        ON n2.code = inc.code
    JOIN analytics.manufacturers man2
        ON man2.[manufacturer] = n2.Производитель
    CROSS APPLY (
        SELECT
              inc.qty * n2.[Цена учетная, руб] AS full_sum,
              (
                  -- оплачено к концу месяца m.MonthStart (по обоим платежам)
                  (CASE 
                       WHEN man2.n_percent IS NOT NULL
                            AND EOMONTH(m.MonthStart)
                                >= DATEADD(day, man2.n_days - 90,
                                           EOMONTH(inc.MonthStart))
                       THEN inc.qty * n2.[Цена учетная, руб] * man2.n_percent
                       ELSE 0
                   END)
                +
                  (CASE 
                       WHEN man2.m_percent IS NOT NULL
                            AND EOMONTH(m.MonthStart)
                                >= DATEADD(day, man2.m_days - 90,
                                           EOMONTH(inc.MonthStart))
                       THEN inc.qty * n2.[Цена учетная, руб] * man2.m_percent
                       ELSE 0
                   END)
              ) AS paid_to_month
    ) calc
)
INSERT INTO #obligations (MonthStart, code, obligation)
SELECT
      MonthStart,
      code,
      SUM(ObligationPerIncoming) AS obligation
FROM ObligRows
GROUP BY
      MonthStart,
      code;

---------------------------------------------------------
-- 8. ТН по месяцам (#tn_monthly) из Продажи_Учёт_Маржа_по_дням
---------------------------------------------------------
IF OBJECT_ID('tempdb..#tn_monthly') IS NOT NULL DROP TABLE #tn_monthly;

CREATE TABLE #tn_monthly (
    MonthStart date          NOT NULL,
    code       nchar(11)     NOT NULL,
    tn_amount  numeric(18,2) NOT NULL,
    CONSTRAINT PK_tn_monthly PRIMARY KEY (MonthStart, code)
);

INSERT INTO #tn_monthly (MonthStart, code, tn_amount)
SELECT
      DATEFROMPARTS(YEAR(p.[d]), MONTH(p.[d]), 1) AS MonthStart,
      p.[Code],
      SUM(p.[Торговая надбавка])                 AS tn_amount
FROM [mag_pbi].[analytics].[Продажи_Учёт_Маржа_по_дням] p
JOIN #s s
    ON s.code = p.[Code]
WHERE p.[d] >= @firstMonthStart
  AND p.[d] <= @lastFullMonth
GROUP BY
      DATEFROMPARTS(YEAR(p.[d]), MONTH(p.[d]), 1),
      p.[Code];

---------------------------------------------------------
-- 9. Финальный SELECT ПО МЕСЯЦАМ (прошлый год)
---------------------------------------------------------
IF OBJECT_ID('tempdb..#month_plan') IS NOT NULL DROP TABLE #month_plan;

SELECT
      s.[1c_id],
      s.code,
      n.Производитель,
      n.[Цена учетная, руб],
      n.[Цена учетная, руб]*(ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0)) AS [Остаток учетка руб],
      man.n_percent,
      man.n_days,
      man.m_percent,
      man.m_days,
      RIGHT('0' + CAST(MONTH(m.MonthStart) AS varchar(2)), 2)
        + '-' + CAST(YEAR(m.MonthStart) AS varchar(4))            AS [месяц],       -- MM-YYYY (текст)
      m.MonthStart                                                AS [MonthStart],  -- реальная дата месяца
      ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0)           AS [остаток],     -- шт
      ISNULL(inc.qty, 0)                                          AS [приход],      -- шт
      ISNULL(pay.amount, 0)                                       AS [платеж],      -- руб
      ISNULL(ob.obligation, 0)                                    AS [Обязательства],-- руб
      CAST(
           CASE 
               WHEN p.paid_raw < 0 THEN 0
               ELSE p.paid_raw
           END
           AS numeric(18,2)
      )                                                           AS [Оплаченный остаток], -- руб
      ISNULL(tn.tn_amount, 0)                                     AS [ТН],          -- торговая надбавка за месяц, руб
      CAST(
           CASE 
               WHEN p.paid_raw < 0 THEN 0
               ELSE p.paid_raw
           END * DAY(EOMONTH(m.MonthStart))
           AS numeric(18,2)
      )                                                           AS [рубли-дни]   -- оплаченный остаток × дней в месяце
INTO #month_plan
FROM #months m
CROSS JOIN #s s

OUTER APPLY (
    SELECT TOP (1) w1.quantity
    FROM pbi.[w_ostatok_da_net] w1
    WHERE w1._IDRREF = s.[1c_id]
      AND w1.dt <= EOMONTH(m.MonthStart)
    ORDER BY w1.dt DESC
) AS intStock

LEFT JOIN #ext_stock ext
    ON  ext.code = s.code
    AND ext.Dt   = EOMONTH(m.MonthStart)

LEFT JOIN #incoming inc
    ON  inc.code      = s.code
    AND inc.MonthStart = m.MonthStart

LEFT JOIN pbi.nomenclature n
    ON n.code = s.code

LEFT JOIN analytics.manufacturers man
    ON man.[manufacturer] = n.Производитель

LEFT JOIN #payments pay
    ON  pay.code      = s.code
    AND pay.MonthStart = m.MonthStart

LEFT JOIN #obligations ob
    ON  ob.code       = s.code
    AND ob.MonthStart  = m.MonthStart

LEFT JOIN #tn_monthly tn
    ON  tn.code       = s.code
    AND tn.MonthStart  = m.MonthStart

CROSS APPLY (   -- сырые оплаченные рубли (до обрезки в 0)
    SELECT
        (ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0))
            * ISNULL(n.[Цена учетная, руб], 0)
        - ISNULL(ob.obligation, 0) AS paid_raw
) p

ORDER BY m.MonthStart;

-- помесячная картина прошлого
SELECT * FROM #month_plan;

---------------------------------------------------------
-- 10. Рентабельность за прошлый год (ROIC как было)
---------------------------------------------------------
DECLARE @roic_past        numeric(38,6);
DECLARE @tn_year          numeric(38,6);
DECLARE @ruble_days_year  numeric(38,6);

SELECT 
    @tn_year          = SUM(t.[ТН]),
    @ruble_days_year  = SUM(t.[рубли-дни])
FROM #month_plan t;

SET @roic_past = CASE 
                    WHEN @ruble_days_year > 0 
                        THEN @tn_year * 365.0 / @ruble_days_year * 100.0
                    ELSE NULL
                 END;

---------------------------------------------------------
-- 10a. Рентабельность по остатку В РУБЛЯХ (год и квартал назад)
--      ТН / средний остаток учетка руб
---------------------------------------------------------
DECLARE @rent_cap_year     numeric(38,6);
DECLARE @rent_cap_quarter  numeric(38,6);

-- год: средний Остаток учетка руб по всем 12 месяцам
DECLARE @avg_cap_year numeric(38,6);

SELECT 
    @avg_cap_year = AVG(NULLIF(t.[Остаток учетка руб], 0.0))
FROM #month_plan t;

IF @avg_cap_year IS NOT NULL AND @avg_cap_year > 0 AND @tn_year IS NOT NULL
    SET @rent_cap_year = @tn_year / @avg_cap_year;
ELSE
    SET @rent_cap_year = NULL;

-- квартал: последние 3 месяца окна
DECLARE @q_start date      = DATEADD(
                                month, -2,
                                DATEFROMPARTS(YEAR(@lastFullMonth), MONTH(@lastFullMonth), 1)
                            );
DECLARE @tn_q        numeric(38,6);
DECLARE @avg_cap_q   numeric(38,6);

SELECT 
    @tn_q      = SUM(t.[ТН]),
    @avg_cap_q = AVG(NULLIF(t.[Остаток учетка руб], 0.0))
FROM #month_plan t
WHERE t.MonthStart >= @q_start;

IF @avg_cap_q IS NOT NULL AND @avg_cap_q > 0 AND @tn_q IS NOT NULL
    SET @rent_cap_quarter = @tn_q / @avg_cap_q;
ELSE
    SET @rent_cap_quarter = NULL;

---------------------------------------------------------
-- 11. Рентабельность остатка на будущий год (ROIC вперёд)
---------------------------------------------------------

-- 11.1. Текущий SKU и его продажи / день
DECLARE @sku_1c_id binary(16);
SELECT TOP(1) @sku_1c_id = [1c_id] FROM #s;

DECLARE @sales_per_day numeric(38,6);      -- Продажи шт / день
DECLARE @sold_year     numeric(38,6);      -- Продано шт за последние 12 мес

-- берём скорость продаж из аналитики за 365 дней (если есть)
SELECT 
    @sales_per_day = a.[Продажи шт / день]
FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a
WHERE a.[1c_id] = @sku_1c_id;

-- продано шт за наши 12 месяцев (по себестоимости)
SELECT 
    @sold_year = SUM(s.[Количество])
FROM pbiProd.[СводныйСебестоимость Для PBI] s
WHERE s.[1c_id]   = @sku_1c_id
  AND s.[Статья]  = N'Реализация'
  AND s.[Период] >= @firstMonthStart
  AND s.[Период] <= @lastFullMonth;

IF (@sales_per_day IS NULL OR @sales_per_day = 0) AND @sold_year IS NOT NULL
BEGIN
    -- fallback, если ещё не прогоняли sp_create_analytics_365
    SET @sales_per_day = @sold_year / 365.0;
END;

-- 11.2. Маржа на штуку по истории
DECLARE @tn_per_unit numeric(38,6);

SET @tn_per_unit = CASE 
                      WHEN @sold_year IS NOT NULL AND @sold_year > 0 
                           THEN @tn_year / @sold_year
                      ELSE NULL
                   END;

-- 11.3. Текущий оплаченный остаток, остаток шт и остаток учетка руб (последний месяц окна)
DECLARE @q_stock           numeric(18,6);  -- текущий остаток шт
DECLARE @paid_stock_today  numeric(18,6);  -- оплаченный остаток в руб
DECLARE @stock_rub_today   numeric(18,6);  -- остаток учетка руб на конец окна

SELECT TOP(1)
      @q_stock           = [остаток],
      @paid_stock_today = [Оплаченный остаток],
      @stock_rub_today  = [Остаток учетка руб]
FROM #month_plan
ORDER BY MonthStart DESC;   -- последний из 12 месяцев = @lastFullMonth

-- 11.4. Сколько успеем продать за будущий год и какая будет ТН

DECLARE @q_future  numeric(18,6);  -- сколько штук реально успеем продать за год
DECLARE @tn_future numeric(18,6);  -- ожидаемая ТН за будущий год

IF @sales_per_day IS NOT NULL AND @sales_per_day > 0
BEGIN
    DECLARE @max_sell_1y numeric(18,6);
    SET @max_sell_1y = @sales_per_day * 365.0;

    SET @q_future = CASE 
                        WHEN @q_stock IS NULL THEN 0
                        WHEN @q_stock <= @max_sell_1y THEN @q_stock
                        ELSE @max_sell_1y
                    END;
END
ELSE
BEGIN
    SET @q_future = 0;
END;

IF @tn_per_unit IS NOT NULL
    SET @tn_future = @tn_per_unit * @q_future;
ELSE
    SET @tn_future = NULL;

-- 11.5. Будущие рубли-дни и ROIC вперёд

DECLARE @days_to_sell       numeric(18,6);
DECLARE @ruble_days_future  numeric(18,6);
DECLARE @roic_future        numeric(38,6);

IF @sales_per_day IS NOT NULL AND @sales_per_day > 0 AND @q_stock IS NOT NULL
    SET @days_to_sell = @q_stock / @sales_per_day;
ELSE
    SET @days_to_sell = NULL;

IF @days_to_sell IS NULL OR @paid_stock_today IS NULL OR @paid_stock_today <= 0 OR @tn_future IS NULL
BEGIN
    SET @roic_future = NULL;
END
ELSE
BEGIN
    IF @days_to_sell >= 365.0
        -- остаток будет лежать весь год
        SET @ruble_days_future = @paid_stock_today * 365.0;
    ELSE
        -- остаток линейно уходит до нуля за days_to_sell
        SET @ruble_days_future = @paid_stock_today * @days_to_sell / 2.0;

    IF @ruble_days_future > 0
        SET @roic_future = @tn_future * 365.0 / @ruble_days_future * 100.0;
    ELSE
        SET @roic_future = NULL;
END;

---------------------------------------------------------
-- 11.6. Рентабельность по остатку В РУБЛЯХ на будущий год
--       (ТН_future / средний остаток учетка руб в горизонте)
---------------------------------------------------------
DECLARE @rent_cap_future   numeric(38,6);

IF @stock_rub_today IS NULL OR @stock_rub_today <= 0 
   OR @q_stock IS NULL OR @q_stock <= 0 
   OR @q_future IS NULL OR @q_future <= 0 
   OR @tn_future IS NULL
BEGIN
    SET @rent_cap_future = NULL;
END
ELSE
BEGIN
    DECLARE @avg_cap_future numeric(38,6);

    -- считаем, что капитал уходит линейно пропорционально количеству:
    -- капитал "на продажу" = stock_rub_today * (q_future / q_stock)
    -- средний капитал = stock_rub_today - 0.5 * капитал_на_продажу
    SET @avg_cap_future =
        @stock_rub_today * (1.0 - (@q_future / @q_stock) / 2.0);

    IF @avg_cap_future > 0
        SET @rent_cap_future = @tn_future / @avg_cap_future;
    ELSE
        SET @rent_cap_future = NULL;
END;

---------------------------------------------------------
-- 11.7. Таблица будущего по месяцам (#future_plan) — как было
---------------------------------------------------------
IF OBJECT_ID('tempdb..#future_plan') IS NOT NULL DROP TABLE #future_plan;

CREATE TABLE #future_plan (
    PeriodStart      date,
    PeriodEnd        date,
    DaysInPeriod     int,
    QtyStart         numeric(18,6),
    Sold             numeric(18,6),
    QtyEnd           numeric(18,6),
    PaidCapitalAvg   numeric(18,6),
    RubleDays        numeric(18,6),
    TN_period        numeric(18,6),
    ROIC_period      numeric(38,6)
);

DECLARE 
    @f_start       date,
    @f_end         date,
    @days          int,
    @rem_qty       numeric(18,6),
    @qty_start     numeric(18,6),
    @qty_end       numeric(18,6),
    @sold          numeric(18,6),
    @capital_start numeric(18,6),
    @capital_end   numeric(18,6),
    @capital_avg   numeric(18,6),
    @tn_period     numeric(18,6),
    @ruble_days_m  numeric(18,6),
    @roic_m        numeric(38,6),
    @cum_days      int;

SET @rem_qty   = ISNULL(@q_stock, 0);
SET @f_start   = @today;
SET @cum_days  = 0;

WHILE @cum_days < 365 AND @rem_qty > 0 AND @sales_per_day IS NOT NULL AND @sales_per_day > 0
BEGIN
    -- конец периода = конец месяца, в котором f_start
    SET @f_end = EOMONTH(@f_start);
    SET @days  = DATEDIFF(day, @f_start, @f_end) + 1;

    -- не выходим за 365 дней горизонта
    IF @cum_days + @days > 365
    BEGIN
        SET @days = 365 - @cum_days;
        SET @f_end = DATEADD(day, @days - 1, @f_start);
    END;

    SET @qty_start = @rem_qty;

    DECLARE @can_sell numeric(18,6);
    SET @can_sell = @sales_per_day * @days;

    SET @sold = CASE 
                    WHEN @rem_qty <= @can_sell THEN @rem_qty
                    ELSE @can_sell
                END;

    SET @qty_end = @qty_start - @sold;

    IF @q_stock IS NOT NULL AND @q_stock > 0 AND @paid_stock_today IS NOT NULL 
    BEGIN
        SET @capital_start = @paid_stock_today * (@qty_start / @q_stock);
        SET @capital_end   = @paid_stock_today * (@qty_end   / @q_stock);
    END
    ELSE
    BEGIN
        SET @capital_start = 0;
        SET @capital_end   = 0;
    END;

    SET @capital_avg = (@capital_start + @capital_end) / 2.0;

    IF @tn_per_unit IS NOT NULL
        SET @tn_period = @tn_per_unit * @sold;
    ELSE
        SET @tn_period = NULL;

    SET @ruble_days_m = @capital_avg * @days;

    IF @ruble_days_m > 0 AND @tn_period IS NOT NULL
        SET @roic_m = @tn_period * 365.0 / @ruble_days_m * 100.0;
    ELSE
        SET @roic_m = NULL;

    INSERT INTO #future_plan (
        PeriodStart, PeriodEnd, DaysInPeriod,
        QtyStart, Sold, QtyEnd,
        PaidCapitalAvg, RubleDays, TN_period, ROIC_period
    )
    VALUES (
        @f_start, @f_end, @days,
        @qty_start, @sold, @qty_end,
        @capital_avg, @ruble_days_m, @tn_period, @roic_m
    );

    SET @rem_qty  = @qty_end;
    SET @cum_days = @cum_days + @days;
    SET @f_start  = DATEADD(day, 1, @f_end);

    IF @cum_days >= 365 BREAK;
END;

---------------------------------------------------------
-- 12. Итог: прошлое и будущее + рентабельность по остатку (руб)
---------------------------------------------------------
SELECT 
    @roic_past        AS [ROIC за год назад],
    @roic_future      AS [ROIC на будущий год],
    @rent_cap_year    AS [Рентабельность по остатку (руб) / год назад],
    @rent_cap_quarter AS [Рентабельность по остатку (руб) / квартал назад],
    @rent_cap_future  AS [Рентабельность по остатку (руб) / будущий год];

-- Таблица будущего по месяцам / периодам (как было)
SELECT * FROM #future_plan ORDER BY PeriodStart;

END
GO

/****** Object:  StoredProcedure [analytics].[sp_run_deficit_all_skus]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [analytics].[sp_run_deficit_all_skus]
	@scenario_id    INT
 as BEGIN
	DELETE FROM [analytics].[deficit_proposal] where scenario_id = @scenario_id

	EXEC [analytics].[sp_build_deficit_proposal]
		 @scenario_id   = @scenario_id,
		 @group_path    = N'',          -- пусто = все группы
		 @lead_time_m   = 4,
		 @cover_months  = 6,
		 @from_month    = '2025-10-01',
		 @to_month_excl = '2028-01-01',
		 @debug         = 0;            -- чтобы не заспамить Messages
END
GO

/****** Object:  StoredProcedure [analytics].[sp_загрузка_прогноза_закупки]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/****** Script for SelectTopNRows command from SSMS  ******/

CREATE PROCEDURE [analytics].[sp_загрузка_прогноза_закупки] AS BEGIN

	SET NOCOUNT ON;

	IF OBJECT_ID('tempdb..#plan2026') IS NOT NULL
		DROP TABLE #plan2026;

	CREATE TABLE #plan2026 (
		code       nvarchar(50) NOT NULL,
		opt        decimal(18,2) NULL,
		mp         decimal(18,2) NULL,
		updated_by nvarchar(100) NULL
	);

	-- уникальный индекс по коду, дубли игнорируем
	CREATE UNIQUE INDEX UX_plan2026_code
	ON #plan2026(code)
	WITH (IGNORE_DUP_KEY = ON);

	BULK INSERT #plan2026
	FROM '\\192.168.35.3\admin3\Обмен\powerbi\plan2026.csv'
	WITH (
		  FIRSTROW        = 2,          -- пропускаем заголовок
		  FIELDTERMINATOR = ';',
		  ROWTERMINATOR   = '\n',
		  CODEPAGE        = '1251',    -- или 1251, если файл не в UTF-8
		  TABLOCK
	);

	-- Проверка
	SELECT COUNT(*)           AS total_rows,
		   COUNT(DISTINCT code) AS distinct_codes
	FROM #plan2026;

	-- Проверка
	SELECT TOP (20) * 
	FROM #plan2026


	SELECT 
		month
		, avg([koef])
	  FROM [mag_pbi].[analytics].[seasonality_groups_summ_1]
	  GROUP by month
	  order by  month




	------------------------------------------------------------
	-- 1. Средняя сезонность по месяцам → #Seasonality
	------------------------------------------------------------
	IF OBJECT_ID('tempdb..#Seasonality') IS NOT NULL
		DROP TABLE #Seasonality;

	SELECT
		  [month]
		, koef = AVG([koef])
	INTO #Seasonality
	FROM [mag_pbi].[analytics].[seasonality_groups_summ_1]
	GROUP BY [month];

	------------------------------------------------------------
	-- 2. Почистить сценарий 8 (если нужно пересчитать)
	------------------------------------------------------------
	DELETE FROM [mag_pbi].[analytics].[forecast]
	WHERE scenario_id = 8;

	------------------------------------------------------------
	-- 3. Записать прогноз в forecast для scenario_id = 8
	--    value = opt_месяц + mp_месяц
	------------------------------------------------------------
	INSERT INTO [mag_pbi].[analytics].[forecast] (
		  scenario_id
		, [1c_id]
		, [code]
		, [month]
		, [value]
		, [updated_at]
		, [updated_by]
		, [opt]
		, [mp]
	)
	SELECT
		  8 AS scenario_id
		, n.[1c_id]
		, p.[code]
		, DATEFROMPARTS(2026, s.[month], 1) AS [month]
		, CAST( (p.opt * s.koef) + (p.mp * s.koef) AS decimal(18,3)) AS [value]
		, GETDATE()                           AS updated_at
		, p.[updated_by]                      AS updated_by
		, CAST(p.opt * s.koef AS decimal(18,3)) AS [opt]
		, CAST(p.mp  * s.koef AS decimal(18,3)) AS [mp]
	FROM #plan2026                     AS p
	INNER JOIN [mag_pbi].[pbi].[nomenclature] AS n
		ON n.[code] = p.[code]
	CROSS JOIN #Seasonality            AS s;
	-- всего строк и разных кодов


	--EXEC [analytics].[sp_build_deficit_proposal] @scenario_id = 8
	/*
			/****** Script for SelectTopNRows command from SSMS  ******/
		INSERT INTO [mag_pbi].[analytics].[forecast] (
			  scenario_id
			, [1c_id]
			, [code]
			, [month]
			, [value]
			, [updated_at]
			, [updated_by]
			, [opt]
			, [mp]
		)
		SELECT 
			  8 AS scenario_id
			  ,[1c_id]
			  ,[code]
			  ,[month]
			  ,[value]
			  ,[updated_at]
			  ,[updated_by]
			  ,[opt]
			  ,[mp]
		  FROM [mag_pbi].[analytics].[forecast]
		  WHERE scenario_id = 5 AND code not in (SELECT code FROM [mag_pbi].[analytics].[forecast] WHERE scenario_id = 8 )
		  */

		  --EXEC [analytics].[sp_fill_deficit_money_request] @scenario_id = 8
		  
END
GO

/****** Object:  StoredProcedure [analytics].[usp_CreateForecastBasesKs]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE   PROCEDURE analytics.usp_CreateForecastBasesKs
AS
BEGIN
    SET NOCOUNT ON;

    -- Удаляем вьюху, если существует
    IF OBJECT_ID('analytics.ForecastBasesKs', 'V') IS NOT NULL
        DROP VIEW analytics.ForecastBasesKs;

    -- Создаём заново
    EXEC('
    CREATE VIEW [analytics].[ForecastBasesKs] AS
    WITH MaxDate AS (
        SELECT MAX(Период) AS max_date
        FROM [pbi].[Себестоимость]
        WHERE Статья = ''реализация''
    ),
    LastFullMonth AS (
        SELECT 
            YEAR(DATEADD(MONTH, -1, max_date)) AS last_year,
            MONTH(DATEADD(MONTH, -1, max_date)) AS last_month
        FROM MaxDate
    ),
    -- 1. Продажи по SKU × месяц
    Sales AS (
        SELECT
            artic_id,
            YEAR(Период) AS Год,
            MONTH(Период) AS Месяц,
            SUM(Количество) AS total_sales
        FROM [pbi].[Себестоимость]
        WHERE Статья = ''реализация''
        GROUP BY artic_id, YEAR(Период), MONTH(Период)
    ),
    -- 2. Дни в продаже
    Stock AS (
        SELECT
            artic_id,
            YEAR(dt) AS Год,
            MONTH(dt) AS Месяц,
            SUM(CASE WHEN ostatok = 1 THEN 1 ELSE 0 END) AS days_available
        FROM [pbi].[w_ostatok_da_net]
        GROUP BY artic_id, YEAR(dt), MONTH(dt)
    ),
    -- 3. База
    Base AS (
        SELECT
            s.artic_id,
            n.code,
            n.description,
            s.Год,
            s.Месяц,
            s.total_sales,
            st.days_available,
            sg.seasonal_koef,
            (s.total_sales / NULLIF(sg.seasonal_koef,0)) AS Normalized_sales,
            CASE WHEN st.days_available > 19 THEN 1 ELSE 0 END AS valid_month,
            CASE WHEN st.days_available > 19 
                 THEN (s.total_sales / NULLIF(sg.seasonal_koef,0)) 
                 ELSE NULL END AS normalized_valid_sales
        FROM Sales s
        LEFT JOIN Stock st
            ON s.artic_id = st.artic_id
           AND s.Год = st.Год
           AND s.Месяц = st.Месяц
        JOIN [pbi].[nomenclature] n
            ON s.artic_id = n.artic_id
        JOIN [analytics].[seasonality_groups] sg
            ON n.[1c_group] = sg.group_1c_id
           AND s.Месяц = sg.month
    ),
    Windowed AS (
        SELECT 
            b.*,
            ROW_NUMBER() OVER (PARTITION BY b.artic_id ORDER BY (b.Год*100 + b.Месяц) DESC) AS rn_desc
        FROM Base b
    ),
    Aggregates AS (
        SELECT 
            w.artic_id,
            AVG(CASE WHEN rn_desc <= 12 THEN normalized_valid_sales END) AS Base_12M,
            CASE 
                WHEN MIN(CASE WHEN rn_desc <= 3 THEN valid_month END) = 1
                THEN AVG(CASE WHEN rn_desc <= 3 THEN normalized_valid_sales END)
                ELSE NULL 
            END AS Base_3M
        FROM Windowed w
        CROSS JOIN LastFullMonth lm
        WHERE (w.Год*100 + w.Месяц) <= (lm.last_year*100 + lm.last_month)
        GROUP BY w.artic_id
    ),
    TrendData AS (
        SELECT 
            w.artic_id,
            ROW_NUMBER() OVER (PARTITION BY w.artic_id ORDER BY (w.Год*100 + w.Месяц)) - 1 AS t,
            LOG(w.normalized_valid_sales) AS ln_sales
        FROM Windowed w
        CROSS JOIN LastFullMonth lm
        WHERE (w.Год*100 + w.Месяц) > (lm.last_year*100 + lm.last_month - 200)
          AND (w.Год*100 + w.Месяц) <= (lm.last_year*100 + lm.last_month)
          AND w.normalized_valid_sales > 0
    ),
    TrendAgg AS (
        SELECT
            artic_id,
            COUNT(*) AS n_obs,
            EXP( (AVG(t*ln_sales) - AVG(t)*AVG(ln_sales)) / NULLIF(AVG(t*t) - AVG(t)*AVG(t),0) ) AS g_raw
        FROM TrendData
        GROUP BY artic_id
        HAVING COUNT(*) >= 6
    ),
    TrendFinal AS (
        SELECT
            artic_id,
            POWER(
                CASE 
                    WHEN POWER(g_raw, 12) < 0.7 THEN 0.7
                    WHEN POWER(g_raw, 12) > 1.5 THEN 1.5
                    ELSE POWER(g_raw, 12)
                END,
                1.0/12
            ) AS Ktrend
        FROM TrendAgg
    ),
    YoYpairs AS (
        SELECT 
            w.artic_id,
            (w.Год*100 + w.Месяц) AS ym,
            w.normalized_valid_sales AS v2,
            LAG(w.normalized_valid_sales, 12) OVER (
                PARTITION BY w.artic_id ORDER BY (w.Год*100 + w.Месяц)
            ) AS v1
        FROM Windowed w
        CROSS JOIN LastFullMonth lm
        WHERE (w.Год*100 + w.Месяц) <= (lm.last_year*100 + lm.last_month)
    ),
    YoYratios AS (
        SELECT artic_id, (v2 / v1) AS ratio
        FROM YoYpairs
        WHERE v1 > 0 AND v2 > 0
    ),
    YoYFinal AS (
        SELECT DISTINCT
            artic_id,
            CASE 
                WHEN PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratio) 
                     OVER (PARTITION BY artic_id) < 0.7 THEN 0.7
                WHEN PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratio) 
                     OVER (PARTITION BY artic_id) > 1.5 THEN 1.5
                ELSE PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratio) 
                     OVER (PARTITION BY artic_id)
            END AS Kgrowth_YoY,
            COUNT(ratio) OVER (PARTITION BY artic_id) AS cnt_pairs
        FROM YoYratios
    ),
    YoYFiltered AS (
        SELECT artic_id, Kgrowth_YoY
        FROM YoYFinal
        WHERE cnt_pairs >= 3
    )
    SELECT 
        n.artic_id,
        n.code,
        n.description,
        a.Base_12M,
        a.Base_3M,
        CASE 
            WHEN a.Base_3M IS NOT NULL THEN a.Base_3M
            ELSE a.Base_12M
        END AS Base_Selected,
        tf.Ktrend,
        yf.Kgrowth_YoY
    FROM pbi.nomenclature n
    LEFT JOIN Aggregates a  ON n.artic_id = a.artic_id
    LEFT JOIN TrendFinal tf ON n.artic_id = tf.artic_id
    LEFT JOIN YoYFiltered yf ON n.artic_id = yf.artic_id;
    ');

END;
GO

/****** Object:  StoredProcedure [analytics].[usp_InsertForecasts]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE   PROCEDURE analytics.usp_InsertForecasts
AS
BEGIN
    SET NOCOUNT ON;

    -- Удаляем старые прогнозы
    DELETE FROM analytics.forecast
    WHERE scenario_id IN (5, 6);

    -- Чистим временные таблицы
    IF OBJECT_ID('tempdb..#Params') IS NOT NULL DROP TABLE #Params;
    IF OBJECT_ID('tempdb..#Calendar') IS NOT NULL DROP TABLE #Calendar;

    -- #Params
    SELECT DATEFROMPARTS(
               YEAR(DATEADD(MONTH, -1, MAX(Период))),
               MONTH(DATEADD(MONTH, -1, MAX(Период))),
               1
           ) AS LastMonth
    INTO #Params
    FROM pbi.Себестоимость
    WHERE Статья = 'реализация';

    -- #Calendar
    SELECT DATEFROMPARTS(YEAR(d), MONTH(d), 1) AS MonthStart
    INTO #Calendar
    FROM (
        SELECT TOP (1000)
               DATEADD(MONTH, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), 
                       (SELECT LastMonth FROM #Params)) AS d
        FROM master..spt_values
    ) x
    WHERE d <= '2026-12-01';

    -- Вставка Trend (5)
    INSERT INTO analytics.forecast (scenario_id, [1c_id], code, [month], value, updated_at, updated_by)
    SELECT 
        5,
        n.[1c_id],
        f.code,
        c.MonthStart,
        CAST(
            ROUND(
                f.Base_Selected * sg.seasonal_koef *
                POWER(f.Ktrend, DATEDIFF(MONTH, p.LastMonth, c.MonthStart)),
                0
            ) AS numeric(18,0)
        ),
        GETDATE(),
        SUSER_SNAME()
    FROM analytics.ForecastBasesKs f
    JOIN pbi.nomenclature n ON f.artic_id = n.artic_id
    CROSS JOIN #Params p
    JOIN #Calendar c ON c.MonthStart > p.LastMonth
    JOIN analytics.seasonality_groups sg
        ON n.[1c_group] = sg.group_1c_id
       AND sg.[month] = MONTH(c.MonthStart)
    WHERE f.Base_Selected IS NOT NULL
      AND f.Ktrend IS NOT NULL;

    -- Вставка YoY (6)
    INSERT INTO analytics.forecast (scenario_id, [1c_id], code, [month], value, updated_at, updated_by)
    SELECT 
        6,
        n.[1c_id],
        f.code,
        c.MonthStart,
        CAST(
            ROUND(
                f.Base_Selected * sg.seasonal_koef * f.Kgrowth_YoY,
                0
            ) AS numeric(18,0)
        ),
        GETDATE(),
        SUSER_SNAME()
    FROM analytics.ForecastBasesKs f
    JOIN pbi.nomenclature n ON f.artic_id = n.artic_id
    CROSS JOIN #Params p
    JOIN #Calendar c ON c.MonthStart > p.LastMonth
    JOIN analytics.seasonality_groups sg
        ON n.[1c_group] = sg.group_1c_id
       AND sg.[month] = MONTH(c.MonthStart)
    WHERE f.Base_Selected IS NOT NULL
      AND f.Kgrowth_YoY IS NOT NULL;

END;
GO

/****** Object:  StoredProcedure [analytics].[Подготовка таблицы продаж к прогнозу]    Script Date: 2026-02-25 09:15:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE analytics.[Подготовка таблицы продаж к прогнозу] as 
BEGIN
	/* ========= ПАРАМЕТРЫ ========= */
	DECLARE @FromDate date = '2023-01-01';
	DECLARE @ToDate   date = CAST(GETDATE() AS date);

	/* ========= ВСПОМОГАТЕЛЬНЫЕ: НЕДЕЛЯ С ПОНЕДЕЛЬНИКА ========= */
	IF OBJECT_ID('tempdb..#dates') IS NOT NULL DROP TABLE #dates;
	WITH d AS (
	  SELECT @FromDate AS d
	  UNION ALL SELECT DATEADD(day,1,d) FROM d WHERE d < @ToDate
	)
	SELECT d AS [date],
		   DATEADD(day, - (DATEPART(weekday,d) + @@DATEFIRST + 5) % 7, d) AS week_start
	INTO #dates
	FROM d
	OPTION (MAXRECURSION 0);

	CREATE INDEX IX_dates_week ON #dates(week_start);

	/* ========= 1) ПРОДАЖИ (только положительные) ========= */
	IF OBJECT_ID('tempdb..#sales_raw') IS NOT NULL DROP TABLE #sales_raw;

	SELECT
		CAST(s.[Период] AS date)                       AS [date],
		s.[1c_id]                                      AS sku_id,
		CAST(s.[КоличествоУпаковок] AS decimal(18,4))  AS qty
	INTO #sales_raw
	FROM [pbiProd].[СводныйСебестоимость Для PBI] s
	WHERE
		s.[Статья] = N'Реализация'
		AND s.[Вид операции] = N'Расход'
		AND s.[КоличествоУпаковок] > 0
		AND s.[Период] >= @FromDate
		AND s.[Период] <= @ToDate;

	CREATE INDEX IX_sales_raw ON #sales_raw(sku_id, [date]);

	/* ========= 2) ФИЛЬТРУЕМ ПРОДАЖИ ПО НАЛИЧИЮ (ostatok=1 из pbi.w_ostatok_da_net) ========= */
	IF OBJECT_ID('tempdb..#sales_clean_daily') IS NOT NULL DROP TABLE #sales_clean_daily;

	SELECT
		sr.sku_id,
		sr.[date],
		sr.qty
	INTO #sales_clean_daily
	FROM #sales_raw sr
	JOIN [pbi].[w_ostatok_da_net] a
	  ON a.[_IDRREF] = sr.sku_id
	 AND a.dt        = sr.[date]
	 AND a.ostatok   = 1;

	CREATE INDEX IX_sales_clean_daily ON #sales_clean_daily(sku_id, [date]);

	/* ========= 3) АГРЕГАЦИЯ В НЕДЕЛИ ========= */
	/* 3.1 Продажи по неделям */
	IF OBJECT_ID('tempdb..#sales_weekly') IS NOT NULL DROP TABLE #sales_weekly;

	SELECT
		scd.sku_id,
		d.week_start,
		SUM(scd.qty) AS qty_week
	INTO #sales_weekly
	FROM #sales_clean_daily scd
	JOIN #dates d
	  ON d.[date] = scd.[date]
	GROUP BY scd.sku_id, d.week_start;

	CREATE INDEX IX_sales_weekly ON #sales_weekly(sku_id, week_start);

	/* 3.2 Доля доступности по неделям (из pbi.w_ostatok_da_net) */
	IF OBJECT_ID('tempdb..#avail_weekly') IS NOT NULL DROP TABLE #avail_weekly;

	SELECT
		a.[_IDRREF]                  AS sku_id,
		d.week_start,
		SUM(a.ostatok)               AS days_available,
		COUNT(*)                     AS days_total,
		CAST(SUM(a.ostatok) * 1.0 / NULLIF(COUNT(*),0) AS decimal(6,4)) AS availability_rate
	INTO #avail_weekly
	FROM [pbi].[w_ostatok_da_net] a
	JOIN #dates d
	  ON d.[date] = a.dt
	WHERE a.dt BETWEEN @FromDate AND @ToDate
	GROUP BY a.[_IDRREF], d.week_start;

	CREATE INDEX IX_avail_weekly ON #avail_weekly(sku_id, week_start);

	/* ========= 4) СКЛЕЙКА: оставляем недели без продаж, если товар был доступен ========= */
	IF OBJECT_ID('tempdb..#weekly_base') IS NOT NULL DROP TABLE #weekly_base;

	SELECT
		COALESCE(sw.sku_id, aw.sku_id)             AS sku_id,
		COALESCE(sw.week_start, aw.week_start)     AS week_start,
		COALESCE(sw.qty_week, 0)                   AS qty_week,
		COALESCE(aw.availability_rate, 0)          AS availability_rate
	INTO #weekly_base
	FROM #sales_weekly sw
	FULL JOIN #avail_weekly aw
	  ON aw.sku_id = sw.sku_id
	 AND aw.week_start = sw.week_start;

	CREATE INDEX IX_weekly_base ON #weekly_base(sku_id, week_start);

	/* ========= 5) МЕТАДАННЫЕ SKU (cat L1, minAvailableQty) ========= */
	IF OBJECT_ID('tempdb..#sku_meta') IS NOT NULL DROP TABLE #sku_meta;

	SELECT
		n.[1c_id]  AS sku_id,
		n.minAvailableQty,
		CAST(CASE 
			WHEN CHARINDEX(N' | ', g.path) > 0 
				 THEN LEFT(g.path, CHARINDEX(N' | ', g.path) - 1)
			ELSE g.path
		END AS nvarchar(200)) AS category_l1
	INTO #sku_meta
	FROM [mag_pbi].[pbi].[nomenclature] n
	LEFT JOIN [mag_pbi].[pbi].[groups] g
	  ON g.group_id = n.group_id;

	CREATE INDEX IX_sku_meta ON #sku_meta(sku_id);

	/* ========= 6) ВИТРИНА С ЛАГАМИ И ФИЧАМИ (analytics) ========= */
	IF OBJECT_ID('analytics.sales_weekly_features', 'U') IS NOT NULL
		DROP TABLE analytics.sales_weekly_features;

	CREATE TABLE analytics.sales_weekly_features (
		sku_id            varbinary(16) NOT NULL,
		week_start        date          NOT NULL,
		qty_week          decimal(18,4) NOT NULL,
		availability_rate decimal(6,4)  NOT NULL,
		-- календарные
		year_num          int           NOT NULL,
		month_num         tinyint       NOT NULL,
		iso_week          tinyint       NOT NULL,
		quarter_num       tinyint       NOT NULL,
		dow_monday1       tinyint       NOT NULL,
		-- справочники
		category_l1       nvarchar(200) NULL,
		minAvailableQty   decimal(18,4) NULL,
		-- лаги
		lag_w1            decimal(18,4) NULL,
		lag_w2            decimal(18,4) NULL,
		lag_w4            decimal(18,4) NULL,
		lag_w12           decimal(18,4) NULL,
		lag_w26           decimal(18,4) NULL,
		lag_w52           decimal(18,4) NULL,
		-- скользящие средние
		ma4               decimal(18,4) NULL,
		ma12              decimal(18,4) NULL,
		created_at        datetime2      NOT NULL DEFAULT sysutcdatetime(),
		CONSTRAINT PK_sales_weekly_features PRIMARY KEY (sku_id, week_start)
	);

	WITH base AS (
	  SELECT wb.sku_id,
			 wb.week_start,
			 wb.qty_week,
			 wb.availability_rate,
			 YEAR(wb.week_start)               AS year_num,
			 MONTH(wb.week_start)              AS month_num,
			 DATEPART(ISO_WEEK, wb.week_start) AS iso_week,
			 DATEPART(QUARTER, wb.week_start)  AS quarter_num,
			 1                                 AS dow_monday1
	  FROM #weekly_base wb
	),
	enriched AS (
	  SELECT b.*,
			 m.category_l1,
			 m.minAvailableQty
	  FROM base b
	  LEFT JOIN #sku_meta m
		ON m.sku_id = b.sku_id
	),
	lags AS (
	  SELECT 
		e.*,
		LAG(e.qty_week, 1)  OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w1,
		LAG(e.qty_week, 2)  OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w2,
		LAG(e.qty_week, 4)  OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w4,
		LAG(e.qty_week, 12) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w12,
		LAG(e.qty_week, 26) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w26,
		LAG(e.qty_week, 52) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w52,
		CAST(AVG(e.qty_week) OVER (
			PARTITION BY e.sku_id 
			ORDER BY e.week_start 
			ROWS BETWEEN 4  PRECEDING AND 1 PRECEDING
		) AS decimal(18,4)) AS ma4,
		CAST(AVG(e.qty_week) OVER (
			PARTITION BY e.sku_id 
			ORDER BY e.week_start 
			ROWS BETWEEN 12 PRECEDING AND 1 PRECEDING
		) AS decimal(18,4)) AS ma12
	  FROM enriched e
	)
	INSERT INTO analytics.sales_weekly_features (
		sku_id, week_start, qty_week, availability_rate,
		year_num, month_num, iso_week, quarter_num, dow_monday1,
		category_l1, minAvailableQty,
		lag_w1, lag_w2, lag_w4, lag_w12, lag_w26, lag_w52,
		ma4, ma12
	)
	SELECT
		sku_id, week_start, qty_week, availability_rate,
		year_num, month_num, iso_week, quarter_num, dow_monday1,
		category_l1, minAvailableQty,
		lag_w1, lag_w2, lag_w4, lag_w12, lag_w26, lag_w52,
		ma4, ma12
	FROM lags;

	CREATE INDEX IX_sales_weekly_features_sku ON analytics.sales_weekly_features(sku_id, week_start);
	CREATE INDEX IX_sales_weekly_features_cat ON analytics.sales_weekly_features(category_l1, week_start);
END
GO
