Compare commits

..

No commits in common. "master" and "refactor_batch" have entirely different histories.

43 changed files with 980 additions and 2164 deletions

127
WARP.md
View File

@ -1,127 +0,0 @@
# WARP.md
This file provides guidance to WARP (warp.dev) when working with code in this repository.
## Architecture Overview
This is a **pricing engine** that computes product pricing recommendations based on multiple data sources (target prices, historical sales, list prices). The system operates with both **SQL Server** and **PostgreSQL** implementations, providing pricing guidance through structured JSON responses.
### Core Components
**Database-First Architecture**: The system is primarily database-driven with stored procedures/functions as the main business logic layer. Each major component has both SQL Server (`.ms.sql`) and PostgreSQL (`.pg.sql`) implementations.
**Single Price Call Flow**: The main entry point is `single_price_call` which processes one pricing scenario through these stages:
1. **Input enrichment** - Resolves customer/channel/tier from bill-to/ship-to codes
2. **Historical price lookup** - Finds most recent/relevant sales from `lastpricedetail`
3. **Target price lookup** - Gets configured target pricing from `target_prices`
4. **Cost normalization** - Adjusts historical prices across different product data segments
5. **List price lookup** - Retrieves volume-banded list prices from `pricelist_ranged`
6. **Guidance logic** - Computes final recommended price using `guidance_logic`
7. **JSON generation** - Creates structured `ui_json` for frontend consumption
**Multi-Database Support**: Core logic exists in parallel implementations:
- **SQL Server**: `procs/*.ms.sql`, `tables/*.ms.sql`
- **PostgreSQL**: `procs/*.pg.sql`, `tables/*.pg.sql`
## Common Development Commands
### Testing Individual Pricing Scenarios
```sql
-- SQL Server
EXEC pricing.single_price_call
@bill = 'GRIF0001',
@ship = 'GRIF0001',
@part = 'XNS0T1G3G18B096',
@v1ds = 'v1:B..PLT..',
@vol = 9600;
-- PostgreSQL
SELECT ui_json->'data'
FROM pricequote.single_price_call(
'GRIF0001',
'GRIF0001',
'XNS0T1G3G18B096',
'v1:B..PLT..',
9600
);
```
### Running Test Examples
Execute the complete example scenarios:
```powershell
# SQL Server examples
sqlcmd -S server -d database -i example_usage.ms.sql
# PostgreSQL examples
psql -d database -f example_usage.pg.sql
```
### Database Schema Updates
Deploy schema changes in this order:
```powershell
# 1. Tables first
sqlcmd -S server -d database -i tables/target_prices.ms.sql
sqlcmd -S server -d database -i tables/pricelist_ranged.ms.sql
sqlcmd -S server -d database -i tables/lastpricedetail.ms.sql
# 2. Functions/procedures second
sqlcmd -S server -d database -i procs/guidance_logic.ms.sql
sqlcmd -S server -d database -i procs/single_price_call.ms.sql
```
### Data Rebuilds
Refresh core pricing data:
```sql
-- Rebuild price list with UOM conversions
EXEC [script from pricelist_ranged.ms.sql]
-- Refresh historical price summaries
EXEC [script from lastpricedetail.ms.sql]
-- Update target price configurations
INSERT INTO pricing.target_prices SELECT * FROM remote.target_prices_view;
```
## Key Data Structures
**Input Parameters**: All pricing calls require:
- `bill` - Bill-to customer code
- `ship` - Ship-to customer code
- `part` - Product part number
- `v1ds` - Product data segment (version 1)
- `vol` - Volume quantity
**UI JSON Structure**: The output `ui_json` contains:
- `details[]` - Array of pricing panels (History, List, Target Calculation, Guidance)
- `data` - Raw `expl` JSON with all calculation details
**Product Data Segments**: Products have versioned data segments (v1ds/v0ds) that affect pricing:
- Format: `v1:B..PLT..` (color tier, packaging, etc.)
- Cross-segment price normalization uses target price ratios or cost ratios
## Database Schemas
**SQL Server**: Uses `pricing.*` schema
- `pricing.single_price_call` - Main pricing procedure
- `pricing.target_prices` - Configured target pricing rules
- `pricing.lastpricedetail` - Historical price summaries per customer/product group
**PostgreSQL**: Uses `pricequote.*` schema
- `pricequote.single_price_call()` - Main pricing function
- `pricequote.target_prices` - Target pricing configurations
- `pricequote.lastpricedetail` - Historical price data
## Key Business Logic
**Guidance Logic**: Located in `guidance_logic.*sql`, determines final price using precedence:
1. Target price (if available)
2. Last normalized price (prevents price drops)
3. List price (as ceiling)
**Price Normalization**: When historical prices are from different product segments, they're adjusted using either target price ratios or cost ratios to enable fair comparison.
**Volume Banding**: List prices and target prices use volume ranges. The `pricelist_ranged` tables normalize all unit-of-measure to "PC" (pieces) for consistent volume comparisons.
## Archive Directory
Contains legacy TypeScript implementations and older SQL approaches. The `archive/apply_guidance.ts` shows the previous client-side pricing logic that has been moved to database stored procedures/functions.

View File

@ -1,167 +0,0 @@
DROP FUNCTION pricequote.build_pricing_path_base;
CREATE OR REPLACE FUNCTION pricequote.build_pricing_path_base(
_json JSONB
)
RETURNS TABLE (
stlc TEXT
,seq BIGINT
,srtcode TEXT
,ds TEXT
,chan TEXT
,tier TEXT
,vol INT4RANGE
,func TEXT
,val NUMERIC
,price NUMERIC
,math TEXT[]
,lastflag BOOLEAN
)
LANGUAGE plpgsql AS
$$
BEGIN
RETURN QUERY
WITH RECURSIVE
-- 1⃣ Parse JSONB into rows of (entity, attr, val)
parsed AS (
SELECT
e.entity,
COALESCE(e.attr, '') AS attr,
e.val,
e.func
FROM jsonb_to_recordset(_json)
AS e(entity TEXT, attr TEXT, val NUMERIC, func TEXT)
),
-- 2⃣ Attach sequence & func from master option_sequence table
sequenced AS (
SELECT
p.entity,
p.attr,
p.val,
p.func,
s.DOMAIN,
DENSE_RANK() OVER (ORDER BY s.seq) AS seq,
ROW_NUMBER() OVER (PARTITION BY p.entity ORDER BY COALESCE(p.val,0) ASC) srt
FROM parsed p
JOIN pricequote.option_sequence s
ON p.entity = s.entity
)
--select * from sequenced ORDER BY seq, srt
-- 3⃣ Recursively accumulate pricing path
,combos AS (
-- 🚀 Base case: first in sequence
SELECT
s.entity,
s.attr,
s.seq,
to_char(s.srt,'FM000') srtcode,
'' ds,
'' chan,
'' tier,
null::TEXT vol,
s.func,
s.val,
s.val agg,
CASE WHEN s.func = 'Price' THEN s.val ELSE NULL::NUMERIC END AS base,
ARRAY[
CASE
WHEN s.func = 'Price' THEN
RPAD(s.entity || ':' || s.attr, 17, ' ') || ' + ' || LPAD(to_char(s.val, 'FM9999999990.00000'), 10, ' ')
WHEN s.func = 'Factor' THEN
RPAD(s.entity || ':' || s.attr, 17, ' ') || ' + ' || LPAD(to_char(COALESCE(NULLIF(CASE WHEN s.func = 'Price' THEN s.val END, NULL), 0) * (s.val - 1), 'FM9999999990.00000'), 10, ' ')
ELSE
RPAD(s.entity || ':' || s.attr, 17, ' ') || ' ' || LPAD(to_char(s.val, 'FM9999999990.00000'), 10, ' ')
END
] math
FROM
sequenced s
WHERE
s.seq = (SELECT MIN(x.seq) FROM sequenced x)
UNION ALL
-- 🔁 Recursive step: process next in sequence
SELECT
c.entity,
c.attr,
o.seq,
c.srtcode || '.' || to_char(o.srt,'FM000'),
c.ds || CASE WHEN o.DOMAIN = 'Product' THEN '.' || o.attr ELSE '' END,
CASE WHEN o.DOMAIN = 'Channel' THEN o.attr ELSE c.chan END chan,
CASE WHEN o.DOMAIN = 'Tier' THEN o.attr ELSE c.tier END tier,
CASE WHEN o.DOMAIN = 'Volume' THEN o.attr ELSE c.vol END vol,
o.func,
o.val,
CASE
WHEN o.func = 'Price' THEN c.agg + o.val
WHEN o.func = 'Factor' THEN c.agg + COALESCE(c.base, 0) * (o.val - 1)
END agg,
COALESCE(c.base, CASE WHEN o.func = 'Price' THEN o.val ELSE NULL::NUMERIC END) AS base,
CASE WHEN (o.func = 'Price' AND o.val <> 0) OR (o.func = 'Factor' AND o.val <> 1) THEN
c.math ||
ARRAY[
CASE
WHEN o.func = 'Price' THEN
RPAD(o.entity || ':' || o.attr, 17, ' ') || ' + ' || LPAD(to_char(o.val, 'FM9999999990.00000'), 10, ' ')
WHEN o.func = 'Factor' THEN
RPAD(o.entity || ':' || o.attr, 17, ' ') || ' + ' || LPAD(to_char(COALESCE(c.base, 0) * (o.val - 1), 'FM9999999990.00000'), 10, ' ')
ELSE
RPAD(o.entity || ':' || o.attr, 17, ' ') || ' ' || LPAD(to_char(o.val, 'FM9999999990.00000'), 10, ' ')
END
]
ELSE
c.math
END math
FROM
combos c
JOIN sequenced o ON
o.seq = c.seq + 1
)
SELECT
-- c.entity
c.attr
,c.seq
,c.srtcode
,'v1:'||SUBSTRING(c.ds,2,100) ds
,c.chan
,c.tier
-- ,c.vol
,CASE
WHEN c.vol ~ '^[0-9]+-[0-9]+$' THEN
int4range(
split_part(c.vol, '-', 1)::int,
split_part(c.vol, '-', 2)::int,
'[)'
)
WHEN c.vol ~ '^[0-9]+$' THEN
int4range(
c.vol::int,
NULL,
'[)'
)
ELSE NULL
END AS vol
,c.func
,c.val
,c.agg
,c.math
,c.seq = (SELECT max(x.seq) FROM sequenced x) lastflag
FROM
combos c /*WHERE seq = (SELECT max(seq) FROM sequenced)*/
ORDER BY
c.srtcode ASC;
END;
$$;
/*
Anchor:EU170S50 + 0.08
Color Tier:P x 1.30
Branding: + 0.00
Packaging:SLV + 0.00
Suffix:PCR x 1.00
Accessories: + 0.00
Channel:WHS + 0.00
Volume:8 x 1.00
-----------------------
0.104
*/

184
esc.json
View File

@ -1,184 +0,0 @@
[
{
"folder": "01 Gal Can",
"entity": "Anchor",
"attr": "BM.03000",
"val": 0.15,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "B",
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "C",
"val": 1.2,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "L",
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "M",
"val": 1.2,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "P",
"val": 1.2,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "T",
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Branding",
"attr": null,
"val": 0,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Branding",
"attr": "L",
"val": 0.04,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Branding",
"attr": "P",
"val": 0.1,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Packaging",
"attr": "BDL",
"val": 0.005,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Packaging",
"attr": "CSE",
"val": 0.01,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Packaging",
"attr": "PLT",
"val": 0,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Packaging",
"attr": "SLV",
"val": 0.005,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Suffix",
"attr": null,
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Accessories",
"attr": null,
"val": 0,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Channel",
"attr": "DIR",
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Channel",
"attr": "DRP",
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Channel",
"attr": "WHS",
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Tier",
"attr": 1,
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Tier",
"attr": 2,
"val": 1.05,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Tier",
"attr": 3,
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Volume",
"attr": "00-01",
"val": 1.35,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Volume",
"attr": "01-08",
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Volume",
"attr": "24",
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Volume",
"attr": "08-24",
"val": 1.05,
"func": "Factor"
}
]

View File

@ -7,22 +7,12 @@ EXEC pricing.single_price_call
@vol = 9600;
EXEC pricing.single_price_call
@bill = 'FARM0001',
@ship = 'KEYB0001',
@part = 'HCA10000B661100',
@v1ds = 'v1:T..CSE..D',
@bill = 'BFGS0001',
@ship = 'BOBS0002',
@part = 'HTI10754B12B024LXB04',
@v1ds = 'v1:L.L.PLT..',
@vol = 172000;
SELECT g.*
FROM (SELECT
TRY_CAST(.33275 AS NUMERIC(20,5)) AS tprice,
TRY_CAST(.758 AS NUMERIC(20,5)) AS last_price_norm,
TRY_CAST(null AS NUMERIC(20,5)) AS listprice_eff,
TRY_CAST('2025-06-01' AS DATE) AS last_date
) q
CROSS APPLY pricing.guidance_logic(q.tprice, q.last_price_norm, q.listprice_eff, q.last_date, 0.05, 1.0, 1.0) g;
SELECT * FROM pricing.lastpricedetail l WHERE customer = 'HYBELS' AND l.partgroup = 'HZP3E100'
SELECT * FROM pricing.pricelist_ranged pr WHERE pr.jcpart = 'XNS0T1G3G18B096' AND

View File

@ -1,80 +1,42 @@
SELECT
ui_json->'data'
FROM pricequote.single_price_call(
SELECT
ui_json->'details'
FROM pricequote.single_price_call(
'FARM0001',
'KEYB0001',
'HCA10000B661100',
'v1:T..CSE..D',
'HZP3E103E21D050',
'v1:C..BDL..',
50000
) f;
) f
SELECT
ui_json->'details'
FROM pricequote.single_price_call(
'FARM0001',
'KEYB0001',
'HZP3E103E21D050',
'v1:C..BDL..',
50000
) f
SELECT
*, ui_json->'data'
FROM pricequote.single_price_call(
SELECT
ui_json
FROM pricequote.single_price_call(
'BFGS0001',
'SPRI0019',
'INT12040G18C100',
'v1:B..CSE..',
7037
) f;
'BOBS0002',
'HTI10754B12B024LXB04',
'v1:L.L.PLT..',
172000
) f
SELECT * FROM pricequote.lastpricedetail l WHERE customer = 'HYBELS' AND l.partgroup = 'HZP3E100'
SELECT
*, ui_json->'data'
FROM pricequote.single_price_call(
'BELL0039',
'BELL0039',
'XNS0T1G3X19B096PZBND',
'v1:L.P.PLT..',
82420
) f;
SELECT * FROM rlarp.cust WHERE code = 'PACK0009'
-- SELECT * FROM pricequote.lastpricedetail l WHERE customer = 'HYBELS' AND l.partgroup = 'HZP3E100';
--
SELECT
q.billto, q.shipto, q.part, q.v1ds, q.units_each, pc.tprice, pc.tmath
FROM
pricequote.live_quotes q
SELECT
pc.expl
FROM
pricequote.live_quotes
LEFT JOIN LATERAL pricequote.single_price_call(
billto, shipto, part, v1ds, units_each
) pc ON TRUE
WHERE
qid = 113761
-- AND qrn = 4;
create table pricequote.target_prices_base as (
with
expand AS (
SELECT
c.compset,
c.stlc,
c.floor,
b.ds,
b.chan,
b.tier,
b.vol,
b.val,
b.price,
json_pretty(to_json(b.math)) math
FROM
pricequote.core_target c
LEFT JOIN LATERAL pricequote.build_pricing_path_base (options||jsonb_build_object('entity','Anchor','attr',c.stlc,'val',c.floor,'func','Price')) b ON b.lastflag
)
-- select count(*) from expand
select * from expand
) with data;
-- SELECT
-- stlc, ds, chan, tier, vol, price, math
-- FROM
-- pricequote.build_pricing_path_base(
-- $$
-- [{"folder":"01 Gal Can","entity":"Anchor","attr":"BM.03000","val":0.15,"func":"Price"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"B","val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"C","val":1.2,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"L","val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"M","val":1.2,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"P","val":1.2,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"T","val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Branding","attr":null,"val":0,"func":"Price"},{"folder":"01 Gal Can","entity":"Branding","attr":"L","val":0.04,"func":"Price"},{"folder":"01 Gal Can","entity":"Branding","attr":"P","val":0.1,"func":"Price"},{"folder":"01 Gal Can","entity":"Packaging","attr":"BDL","val":0.005,"func":"Price"},{"folder":"01 Gal Can","entity":"Packaging","attr":"CSE","val":0.01,"func":"Price"},{"folder":"01 Gal Can","entity":"Packaging","attr":"PLT","val":0,"func":"Price"},{"folder":"01 Gal Can","entity":"Packaging","attr":"SLV","val":0.005,"func":"Price"},{"folder":"01 Gal Can","entity":"Suffix","attr":null,"val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Accessories","attr":null,"val":0,"func":"Price"},{"folder":"01 Gal Can","entity":"Channel","attr":"DIR","val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Channel","attr":"DRP","val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Channel","attr":"WHS","val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Tier","attr":1,"val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Tier","attr":2,"val":1.05,"func":"Factor"},{"folder":"01 Gal Can","entity":"Tier","attr":3,"val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Volume","attr":"00-01","val":1.35,"func":"Factor"},{"folder":"01 Gal Can","entity":"Volume","attr":"01-08","val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Volume","attr":"24","val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Volume","attr":"08-24","val":1.05,"func":"Factor"}]
-- $$
-- )
-- WHERE
-- lastflag
WHERE
qid = 113278
AND qrn = 5

View File

@ -1,81 +0,0 @@
-- CREATE OR ALTER FUNCTION pricing.approval_logic;
CREATE OR ALTER FUNCTION pricing.approval_logic(
@target numeric(20,5),
@last_norm numeric(20,5),
@list_eff numeric(20,5),
@last_date date,
@floor_pct numeric(10,5) = 0.95,
@cap_last_pct numeric(10,5) = 1.00,
@cap_list_pct numeric(10,5) = 1.00
)
RETURNS @ret TABLE (
approval_price numeric(20,5),
approval_reason nvarchar(4000)
)
AS
BEGIN
DECLARE
@base_price numeric(20,5), -- starting point (target if available, else last_norm, else list_eff)
@after_floor numeric(20,5), -- base but limited to x% lower than last price
@after_cap_last numeric(20,5), -- previous step but limited to x% higher than last price
@final_price numeric(20,5), -- previous step but limited to x% higher than list price
@reason nvarchar(4000) = N''; -- logic source of price
-- Early exit if nothing to work with
IF @target IS NULL AND @last_norm IS NULL AND @list_eff IS NULL
BEGIN
INSERT INTO @ret VALUES (NULL, N'No target, last, or list available');
RETURN;
END;
-- Pick starting base price
SET @base_price = COALESCE(@target, @last_norm, @list_eff);
-- Step 1: use base price unless it's more than x% below last price
SET @after_floor = @base_price;
IF @last_norm IS NOT NULL
SET @after_floor = ROUND(
CASE WHEN @base_price >= @last_norm*@floor_pct
THEN @base_price
ELSE @last_norm*@floor_pct
END, 5);
-- Step 2: use price from previous step but don't allow it to be x% above last price
SET @after_cap_last = @after_floor;
IF @last_norm IS NOT NULL
SET @after_cap_last = ROUND(
CASE WHEN @after_floor <= @last_norm*@cap_last_pct
THEN @after_floor
ELSE @last_norm*@cap_last_pct
END, 5);
-- Step 3: use price from last step, but don't allow it to be more than x% above list price
SET @final_price = @after_cap_last;
IF @list_eff IS NOT NULL
SET @final_price = ROUND(
CASE WHEN @after_cap_last <= @list_eff*@cap_list_pct
THEN @after_cap_last
ELSE @list_eff*@cap_list_pct
END, 5);
-- Reason text
SET @reason =
CASE
WHEN @target IS NOT NULL THEN N'Using target price'
WHEN @last_norm IS NOT NULL THEN N'Using last price as base'
WHEN @list_eff IS NOT NULL THEN N'Using list price as base'
END;
IF @last_norm IS NOT NULL AND @after_floor > @base_price
SET @reason = N'Last price drop limit';
IF @last_norm IS NOT NULL AND @after_cap_last < @after_floor
SET @reason = N'Last price increase limit';
IF @list_eff IS NOT NULL AND @final_price < @after_cap_last
SET @reason = N'List price ceiling';
INSERT INTO @ret VALUES (@final_price, @reason);
RETURN;
END;

View File

@ -1,72 +0,0 @@
CREATE OR REPLACE FUNCTION pricequote.approval_logic(
_target numeric(20,5),
_last_norm numeric(20,5),
_list_eff numeric(20,5),
_last_date date,
_floor_pct numeric(10,5) DEFAULT 0.95,
_cap_last_pct numeric(10,5) DEFAULT 1.00,
_cap_list_pct numeric(10,5) DEFAULT 1.00
)
RETURNS TABLE (
approval_price numeric(20,5),
approval_reason text
)
LANGUAGE plpgsql AS $$
DECLARE
base_price numeric(20,5);
after_floor numeric(20,5);
after_cap_last numeric(20,5);
final_price numeric(20,5);
reason text := '';
BEGIN
-- Early exit if nothing to work with
IF _target IS NULL AND _last_norm IS NULL AND _list_eff IS NULL THEN
RETURN QUERY SELECT NULL::numeric, 'No target, last, or list available';
RETURN;
END IF;
-- Pick starting base price
base_price := COALESCE(_target, _last_norm, _list_eff);
-- Step 1: use base price unless it's more than x% below last price
after_floor := base_price;
IF _last_norm IS NOT NULL THEN
after_floor := GREATEST(base_price, ROUND(_last_norm*_floor_pct,5));
END IF;
-- Step 2: use price from previous step but don't allow it to be x% above last price
after_cap_last := after_floor;
IF _last_norm IS NOT NULL THEN
after_cap_last := LEAST(after_floor, ROUND(_last_norm*_cap_last_pct,5));
END IF;
-- cap to list (binds if it lowers price)
final_price := after_cap_last;
IF _list_eff IS NOT NULL THEN
final_price := LEAST(after_cap_last, ROUND(_list_eff*_cap_list_pct,5));
END IF;
-- Reason text
reason :=
CASE
WHEN _target IS NOT NULL THEN 'Using target price'
WHEN _last_norm IS NOT NULL THEN 'Using last price as base'
WHEN _list_eff IS NOT NULL THEN 'Using list price as base'
END;
IF _last_norm IS NOT NULL AND after_floor > base_price THEN
reason := 'Last price drop limit';
END IF;
IF _last_norm IS NOT NULL AND after_cap_last < after_floor THEN
reason := 'Last price increase limit';
END IF;
IF _list_eff IS NOT NULL AND final_price < after_cap_last THEN
reason := 'List price ceiling';
END IF;
RETURN QUERY SELECT final_price, reason;
END $$;

View File

@ -1,61 +1,119 @@
-- CREATE OR ALTER FUNCTION pricing.guidance_logic;
CREATE OR ALTER FUNCTION pricing.guidance_logic(
@target numeric(20,5),
@last_norm numeric(20,5),
@list_eff numeric(20,5),
@last_date date,
@floor_pct numeric(10,5) = 0.95,
@cap_last_pct numeric(10,5) = 1.00,
@cap_list_pct numeric(10,5) = 1.00
-- This function returns the least of two NUMERIC(20,5) values.
CREATE OR ALTER FUNCTION dbo.LEAST_NUMERIC205(
@a NUMERIC(20,5),
@b NUMERIC(20,5)
)
RETURNS @ret TABLE (
guidance_price numeric(20,5),
guidance_reason nvarchar(4000)
RETURNS NUMERIC(20,5)
AS
BEGIN
RETURN CASE
WHEN @a IS NULL THEN @b
WHEN @b IS NULL THEN @a
WHEN @a < @b THEN @a ELSE @b
END
END
GO
-- This function returns the greatest of two NUMERIC(20,5) values.
CREATE OR ALTER FUNCTION dbo.GREATEST_NUMERIC205(
@a NUMERIC(20,5),
@b NUMERIC(20,5)
)
RETURNS NUMERIC(20,5)
AS
BEGIN
RETURN CASE
WHEN @a IS NULL THEN @b
WHEN @b IS NULL THEN @a
WHEN @a > @b THEN @a ELSE @b
END
END
GO
-- This function implements the guidance logic for pricing based on target, last, and list prices.
CREATE OR ALTER FUNCTION pricing.guidance_logic (
@target_price NUMERIC(20,5),
@last_price NUMERIC(20,5),
@list_price NUMERIC(20,5),
@last_date DATE
)
RETURNS @result TABLE (
guidance_price NUMERIC(20,5),
guidance_reason NVARCHAR(MAX)
)
AS
BEGIN
DECLARE
@base_price numeric(20,5), -- starting point (target if available, else last_norm, else list_eff)
@after_floor numeric(20,5), -- base but limited to x% lower than last price
@after_cap_last numeric(20,5), -- previous step but limited to x% higher than last price
@final_price numeric(20,5), -- previous step but limited to x% higher than list price
@reason nvarchar(4000) = N''; -- logic source of price
DECLARE @price NUMERIC(20,5);
DECLARE @reason NVARCHAR(MAX) = '';
DECLARE @floored NUMERIC(20,5);
DECLARE @capped NUMERIC(20,5);
DECLARE @use_last_price BIT = 0;
-- Early exit if nothing to work with
IF @target IS NULL AND @last_norm IS NULL AND @list_eff IS NULL
-- Determine if last price is recent (within last 2 years)
IF @last_price IS NOT NULL AND @last_date IS NOT NULL AND @last_date > DATEADD(YEAR, -2, CAST(GETDATE() AS DATE))
SET @use_last_price = 1;
IF @target_price IS NOT NULL AND @use_last_price = 1
BEGIN
INSERT INTO @ret VALUES (NULL, N'No target, last, or list available');
RETURN;
END;
SET @floored = dbo.GREATEST_NUMERIC205(@target_price, @last_price * 0.95);
SET @capped = dbo.LEAST_NUMERIC205(@floored, @last_price);
SET @price = dbo.LEAST_NUMERIC205(ISNULL(@list_price, 1e9), @capped);
-- Pick starting base price
SET @base_price = COALESCE(@target, @last_norm, @list_eff);
-- Reason text
SET @reason =
CASE
WHEN @target IS NOT NULL THEN N'Using target price'
WHEN @last_norm IS NOT NULL THEN N'Using last price as base'
WHEN @list_eff IS NOT NULL THEN N'Using list price as base'
END;
-- Step 1: use base price less than last price, use last price
IF @base_price < @last_norm
IF @price = @last_price
BEGIN
SET @base_price = @last_norm;
SET @reason = N'Last price';
SET @reason = 'Cap at last price';
END
ELSE
BEGIN
SET @reason = 'Using target price';
IF @target_price < @last_price * 0.95
SET @reason += ', floored to 5% below last price';
IF @target_price > @last_price
SET @reason += ', capped to not exceed last price';
IF @list_price IS NOT NULL AND @price = @list_price AND @target_price > @list_price
SET @reason += ', capped to not exceed list price';
END
END
ELSE IF @use_last_price = 1
BEGIN
SET @price = @last_price;
SET @reason = 'Last price - no target';
END
ELSE IF @target_price IS NOT NULL
BEGIN
SET @price = @target_price;
IF @last_price IS NOT NULL AND @last_date IS NOT NULL
BEGIN
SET @reason = CONCAT(
'Last price ignored (too old: ',
CONVERT(NVARCHAR(10), @last_date, 120),
'), using target price'
);
END
ELSE
BEGIN
SET @reason = 'Target price - no prior sale';
END
END
ELSE
BEGIN
SET @price = NULL;
IF @last_price IS NOT NULL AND @last_date IS NOT NULL
BEGIN
SET @reason = CONCAT(
'Last price ignored (too old: ',
CONVERT(NVARCHAR(10), @last_date, 120),
'), no pricing available'
);
END
ELSE
BEGIN
SET @reason = 'No pricing available';
END
END
-- Step 2: use price from previous step but don't allow it to be x% above last price
IF @base_price > @list_eff
BEGIN
SET @base_price = @list_eff;
SET @reason = N'List price ceiling';
END
SET @final_price = @base_price;
INSERT INTO @ret VALUES (@final_price, @reason);
INSERT INTO @result VALUES (@price, @reason);
RETURN;
END;
END
GO

View File

@ -1,56 +1,76 @@
CREATE OR REPLACE FUNCTION pricequote.guidance_logic(
_target numeric(20,5),
_last_norm numeric(20,5),
_list_eff numeric(20,5),
_last_date date,
_floor_pct numeric(10,5) DEFAULT 0.95,
_cap_last_pct numeric(10,5) DEFAULT 1.00,
_cap_list_pct numeric(10,5) DEFAULT 1.00
_target_price NUMERIC(20,5),
_last_price NUMERIC(20,5),
_list_price NUMERIC(20,5),
_last_date DATE
)
RETURNS TABLE (
guidance_price numeric(20,5),
guidance_reason text
)
LANGUAGE plpgsql AS $$
guidance_price NUMERIC(20,5),
guidance_reason TEXT
) AS $$
DECLARE
base_price numeric(20,5);
after_floor numeric(20,5);
after_cap_last numeric(20,5);
final_price numeric(20,5);
reason text := '';
_price NUMERIC(20,5);
_reason TEXT := '';
_floored NUMERIC(20,5);
_capped NUMERIC(20,5);
_use_last_price BOOLEAN := FALSE;
BEGIN
-- Early exit if nothing to work with
IF _target IS NULL AND _last_norm IS NULL AND _list_eff IS NULL THEN
RETURN QUERY SELECT NULL::numeric, 'No target, last, or list available';
RETURN;
-- Evaluate whether last price is recent enough
IF _last_price IS NOT NULL AND _last_date IS NOT NULL AND _last_date > CURRENT_DATE - INTERVAL '2 years' THEN
_use_last_price := TRUE;
END IF;
IF _target_price IS NOT NULL AND _use_last_price THEN
_floored := GREATEST(_target_price, _last_price * 0.95);
_capped := LEAST(_floored, _last_price);
_price := LEAST(COALESCE(_list_price, 1e9), _capped);
-- Pick starting base price
base_price := COALESCE(_target, _last_norm, _list_eff);
-- Reason text
reason :=
CASE
WHEN _target IS NOT NULL THEN 'Using target price'
WHEN _last_norm IS NOT NULL THEN 'Using last price as base'
WHEN _list_eff IS NOT NULL THEN 'Using list price as base'
END;
-- Step 1: use base price less than last price, use last price
IF base_price < _last_norm THEN
base_price := _last_norm;
reason := 'Last price';
IF _price = _last_price THEN
_reason := 'Cap at last price';
ELSE
_reason := 'Using target price';
IF _target_price < _last_price * 0.95 THEN
_reason := _reason || ', floored to 5% below last price';
END IF;
IF _target_price > _last_price THEN
_reason := _reason || ', capped to not exceed last price';
END IF;
IF _list_price IS NOT NULL AND _price = _list_price AND _target_price > _list_price THEN
_reason := _reason || ', capped to not exceed list price';
END IF;
END IF;
-- Step 2: use price from previous step but don't allow it to be x% above last price
IF base_price > _list_eff THEN
base_price := _list_eff;
reason := 'List price ceiling';
ELSIF _use_last_price THEN
_price := _last_price;
_reason := 'Last price - no target';
ELSIF _target_price IS NOT NULL THEN
_price := _target_price;
IF _last_price IS NOT NULL AND _last_date IS NOT NULL THEN
_reason := format(
-- 'Last price ignored (too old: %s), using target price',
'Last price ignored, using target price',
to_char(_last_date, 'YYYY-MM-DD')
);
ELSE
_reason := 'Target price - no prior sale';
END IF;
final_price := base_price;
ELSE
_price := NULL;
RETURN QUERY SELECT final_price, reason;
END $$;
IF _last_price IS NOT NULL AND _last_date IS NOT NULL THEN
_reason := format(
-- 'Last price ignored (too old: %s), no pricing available',
'Last price too old, no pricing available',
to_char(_last_date, 'YYYY-MM-DD')
);
ELSE
_reason := 'No pricing available';
END IF;
END IF;
RETURN QUERY SELECT _price, _reason;
END;
$$ LANGUAGE plpgsql;

View File

@ -15,7 +15,7 @@ RETURNS @result TABLE (
)
AS
BEGIN
DECLARE @age_threshold DATE = DATEADD(month, -18, GETDATE());
DECLARE @age_threshold DATE = DATEADD(year, -1, GETDATE());
-- Extract all relevant objects from JSON
DECLARE @dsq NVARCHAR(MAX), @dss NVARCHAR(MAX), @mrq NVARCHAR(MAX), @mrs NVARCHAR(MAX);
@ -51,57 +51,39 @@ BEGIN
-- Use the same selection logic as before
-- 1. Prefer the most recent of dss/dsq if either is within the age threshold
IF (@dss_date IS NOT NULL AND @dss_date > @age_threshold)
IF (@dsq_date IS NOT NULL AND @dsq_date > @age_threshold)
OR (@dss_date IS NOT NULL AND @dss_date > @age_threshold)
BEGIN
IF @dsq_date IS NOT NULL AND (@dss_date IS NULL OR @dsq_date >= @dss_date) AND @dsq_date > @age_threshold
INSERT INTO @result VALUES (@dsq_price, 'dsq', @dsq_date, @dsq_qty, @dsq_dataseg, @dsq_ord, @dsq_quote, @dsq_part);
ELSE IF @dss_date IS NOT NULL AND @dss_date > @age_threshold
INSERT INTO @result VALUES (@dss_price, 'dss', @dss_date, @dss_qty, @dss_dataseg, @dss_ord, @dss_quote, @dss_part);
RETURN;
END
-- IF (@dsq_date IS NOT NULL AND @dsq_date > @age_threshold)
-- OR (@dss_date IS NOT NULL AND @dss_date > @age_threshold)
-- BEGIN
-- IF @dsq_date IS NOT NULL AND (@dss_date IS NULL OR @dsq_date >= @dss_date) AND @dsq_date > @age_threshold
-- INSERT INTO @result VALUES (@dsq_price, 'dsq', @dsq_date, @dsq_qty, @dsq_dataseg, @dsq_ord, @dsq_quote, @dsq_part);
-- ELSE IF @dss_date IS NOT NULL AND @dss_date > @age_threshold
-- INSERT INTO @result VALUES (@dss_price, 'dss', @dss_date, @dss_qty, @dss_dataseg, @dss_ord, @dss_quote, @dss_part);
-- RETURN;
-- END
-- 2. If both dss/dsq are older than the threshold, use the most recent of mrs/mrq if either exists
IF @mrs_date IS NOT NULL AND @mrs_date > @age_threshold
IF (@mrq_date IS NOT NULL OR @mrs_date IS NOT NULL)
BEGIN
IF @mrq_date IS NOT NULL AND (@mrs_date IS NULL OR @mrq_date >= @mrs_date)
INSERT INTO @result VALUES (@mrq_price, 'mrq', @mrq_date, @mrq_qty, @mrq_dataseg, @mrq_ord, @mrq_quote, @mrq_part);
ELSE IF @mrs_date IS NOT NULL
INSERT INTO @result VALUES (@mrs_price, 'mrs', @mrs_date, @mrs_qty, @mrs_dataseg, @mrs_ord, @mrs_quote, @mrs_part);
RETURN;
END
-- IF (@mrq_date IS NOT NULL OR @mrs_date IS NOT NULL)
-- BEGIN
-- IF @mrq_date IS NOT NULL AND (@mrs_date IS NULL OR @mrq_date >= @mrs_date)
-- INSERT INTO @result VALUES (@mrq_price, 'mrq', @mrq_date, @mrq_qty, @mrq_dataseg, @mrq_ord, @mrq_quote, @mrq_part);
-- ELSE IF @mrs_date IS NOT NULL
-- INSERT INTO @result VALUES (@mrs_price, 'mrs', @mrs_date, @mrs_qty, @mrs_dataseg, @mrs_ord, @mrs_quote, @mrs_part);
-- RETURN;
-- END
-- 3. If all are at least as old as the threshold, pick the least oldest price available
-- DECLARE
-- @best_price NUMERIC(20,5) = NULL
-- ,@best_source NVARCHAR(10) = NULL
-- ,@best_date DATE = NULL
-- ,@best_qty NUMERIC(20,5) = NULL
-- ,@best_dataseg NVARCHAR(100) = NULL
-- ,@best_ord NVARCHAR(20) = NULL
-- ,@best_quote NVARCHAR(20) = NULL
-- ,@best_part NVARCHAR(100) = NULL;
-- IF @dsq_date IS NOT NULL
-- SELECT @best_price = @dsq_price, @best_source = 'dsq', @best_date = @dsq_date, @best_qty = @dsq_qty, @best_dataseg = @dsq_dataseg, @best_ord = @dsq_ord, @best_quote = @dsq_quote, @best_part = @dsq_part;
-- IF @dss_date IS NOT NULL AND (@best_date IS NULL OR @dss_date > @best_date)
-- SELECT @best_price = @dss_price, @best_source = 'dss', @best_date = @dss_date, @best_qty = @dss_qty, @best_dataseg = @dss_dataseg, @best_ord = @dss_ord, @best_quote = @dss_quote, @best_part = @dss_part;
-- IF @mrq_date IS NOT NULL AND (@best_date IS NULL OR @mrq_date > @best_date)
-- SELECT @best_price = @mrq_price, @best_source = 'mrq', @best_date = @mrq_date, @best_qty = @mrq_qty, @best_dataseg = @mrq_dataseg, @best_ord = @mrq_ord, @best_quote = @mrq_quote, @best_part = @mrq_part;
-- IF @mrs_date IS NOT NULL AND (@best_date IS NULL OR @mrs_date > @best_date)
-- SELECT @best_price = @mrs_price, @best_source = 'mrs', @best_date = @mrs_date, @best_qty = @mrs_qty, @best_dataseg = @mrs_dataseg, @best_ord = @mrs_ord, @best_quote = @mrs_quote, @best_part = @mrs_part;
DECLARE @best_price NUMERIC(20,5) = NULL, @best_source NVARCHAR(10) = NULL, @best_date DATE = NULL, @best_qty NUMERIC(20,5) = NULL, @best_dataseg NVARCHAR(100) = NULL, @best_ord NVARCHAR(20) = NULL, @best_quote NVARCHAR(20) = NULL, @best_part NVARCHAR(100) = NULL;
IF @dsq_date IS NOT NULL
SELECT @best_price = @dsq_price, @best_source = 'dsq', @best_date = @dsq_date, @best_qty = @dsq_qty, @best_dataseg = @dsq_dataseg, @best_ord = @dsq_ord, @best_quote = @dsq_quote, @best_part = @dsq_part;
IF @dss_date IS NOT NULL AND (@best_date IS NULL OR @dss_date > @best_date)
SELECT @best_price = @dss_price, @best_source = 'dss', @best_date = @dss_date, @best_qty = @dss_qty, @best_dataseg = @dss_dataseg, @best_ord = @dss_ord, @best_quote = @dss_quote, @best_part = @dss_part;
IF @mrq_date IS NOT NULL AND (@best_date IS NULL OR @mrq_date > @best_date)
SELECT @best_price = @mrq_price, @best_source = 'mrq', @best_date = @mrq_date, @best_qty = @mrq_qty, @best_dataseg = @mrq_dataseg, @best_ord = @mrq_ord, @best_quote = @mrq_quote, @best_part = @mrq_part;
IF @mrs_date IS NOT NULL AND (@best_date IS NULL OR @mrs_date > @best_date)
SELECT @best_price = @mrs_price, @best_source = 'mrs', @best_date = @mrs_date, @best_qty = @mrs_qty, @best_dataseg = @mrs_dataseg, @best_ord = @mrs_ord, @best_quote = @mrs_quote, @best_part = @mrs_part;
-- IF @best_price IS NOT NULL
-- INSERT INTO @result VALUES (@best_price, @best_source, @best_date, @best_qty, @best_dataseg, @best_ord, @best_quote, @best_part);
IF @best_price IS NOT NULL
INSERT INTO @result VALUES (@best_price, @best_source, @best_date, @best_qty, @best_dataseg, @best_ord, @best_quote, @best_part);
RETURN;
END

View File

@ -12,7 +12,7 @@ DECLARE
BEGIN
-- Central control for age threshold
DECLARE
age_threshold INTERVAL := INTERVAL '18 months';
age_threshold INTERVAL := INTERVAL '1 year';
dsq_date DATE := NULL;
dss_date DATE := NULL;
mrq_date DATE := NULL;
@ -35,31 +35,40 @@ BEGIN
END IF;
-- 1. Prefer the most recent of dss/dsq if either is within the age threshold
IF dss_date IS NOT NULL AND dss_date > (CURRENT_DATE - age_threshold) THEN
IF (dsq_date IS NOT NULL AND dsq_date > (CURRENT_DATE - age_threshold))
OR (dss_date IS NOT NULL AND dss_date > (CURRENT_DATE - age_threshold)) THEN
IF dsq_date IS NOT NULL AND (dss_date IS NULL OR dsq_date >= dss_date) AND dsq_date > (CURRENT_DATE - age_threshold) THEN
result := dsq || jsonb_build_object('source', 'dsq');
ELSIF dss_date IS NOT NULL AND dss_date > (CURRENT_DATE - age_threshold) THEN
result := dss || jsonb_build_object('source', 'dss');
END IF;
-- 2. If both dss/dsq are older than the threshold, use the most recent of mrs/mrq if either exists
ELSIF mrs_date IS NOT NULL AND mrs_date > (CURRENT_DATE - age_threshold) THEN
ELSIF (mrq_date IS NOT NULL OR mrs_date IS NOT NULL) THEN
IF mrq_date IS NOT NULL AND (mrs_date IS NULL OR mrq_date >= mrs_date) THEN
result := mrq || jsonb_build_object('source', 'mrq');
ELSIF mrs_date IS NOT NULL THEN
result := mrs || jsonb_build_object('source', 'mrs');
END IF;
-- 3. If all are at least as old as the threshold, pick the least oldest price available
ELSE
best := NULL;
best_date := NULL;
-- IF dsq_date IS NOT NULL THEN
-- best := dsq || jsonb_build_object('source', 'dsq');
-- best_date := dsq_date;
-- END IF;
-- IF dss_date IS NOT NULL AND (best_date IS NULL OR dss_date > best_date) THEN
-- best := dss || jsonb_build_object('source', 'dss');
-- best_date := dss_date;
-- END IF;
-- IF mrq_date IS NOT NULL AND (best_date IS NULL OR mrq_date > best_date) THEN
-- best := mrq || jsonb_build_object('source', 'mrq');
-- best_date := mrq_date;
-- END IF;
-- IF mrs_date IS NOT NULL AND (best_date IS NULL OR mrs_date > best_date) THEN
-- best := mrs || jsonb_build_object('source', 'mrs');
-- best_date := mrs_date;
-- END IF;
IF dsq_date IS NOT NULL THEN
best := dsq || jsonb_build_object('source', 'dsq');
best_date := dsq_date;
END IF;
IF dss_date IS NOT NULL AND (best_date IS NULL OR dss_date > best_date) THEN
best := dss || jsonb_build_object('source', 'dss');
best_date := dss_date;
END IF;
IF mrq_date IS NOT NULL AND (best_date IS NULL OR mrq_date > best_date) THEN
best := mrq || jsonb_build_object('source', 'mrq');
best_date := mrq_date;
END IF;
IF mrs_date IS NOT NULL AND (best_date IS NULL OR mrs_date > best_date) THEN
best := mrs || jsonb_build_object('source', 'mrs');
best_date := mrs_date;
END IF;
result := best;
END IF;
RETURN result;

View File

@ -46,8 +46,6 @@ CREATE TABLE pricequote.queue (
list_relevance TEXT,
guidance_price NUMERIC,
guidance_reason TEXT,
approval_price NUMERIC,
approval_reason TEXT,
expl JSONB,
ui_json JSONB
);
@ -68,7 +66,6 @@ BEGIN
-- 1) Seed queue from matrix
--------------------------------------------------------------------
DELETE FROM pricequote.queue;
-- 1:30
INSERT INTO pricequote.queue (bill, ship, part, stlc, v1ds, vol, expl, ui_json)
SELECT DISTINCT
@ -87,7 +84,7 @@ BEGIN
AND o.version IN ('Actual', 'Forecast', 'Quotes')
AND o.part IS NOT NULL
AND SUBSTRING(o.glec, 1, 1) <= '2';
-- 2:12 0:38
-- 46 seconds
--------------------------------------------------------------------
-- 2) Enrich: chan, tier, cust, pltq, plevel, partgroup (+stlc fix)
@ -129,7 +126,7 @@ BEGIN
plevel = s.plevel,
partgroup = s.partgroup,
stlc = COALESCE(q.stlc, s.stlc_fix);
-- 4:51 0:17
-- 16 seconds
--------------------------------------------------------------------
-- 3) Scenario fields from item master: part_v1ds, v0ds, orig costs
@ -149,7 +146,7 @@ BEGIN
END
FROM "CMS.CUSLG".itemm i0
WHERE i0.item = q.part;
-- 3:21 0:20
-- 16 seconds
--------------------------------------------------------------------
-- 4) History: store hist, extract last_* with precedence helper
@ -187,7 +184,7 @@ BEGIN
AND lp2.partgroup = q2.partgroup
) AS x
WHERE q.ctid = x.ctid;
-- 7:32 1:31
-- 2 min 3 sec
--------------------------------------------------------------------
-- 5) Target (requested v1ds): tprice, tmath, volume_range
@ -197,14 +194,14 @@ BEGIN
tprice = tp.price,
tmath = to_json(tp.math),
volume_range = tp.vol::TEXT
FROM pricequote.target_prices_base tp
FROM pricequote.target_prices tp
WHERE
tp.stlc = q.stlc
AND tp.ds = q.v1ds
AND tp.chan = q.chan
AND tp.tier = q.tier
AND FLOOR(q.vol / NULLIF(q.pltq, 0))::INT <@ tp.vol;
-- 2:51 0:15
-- 22 seconds
--------------------------------------------------------------------
-- 6) Target for last_dataseg (tprice_last)
@ -212,15 +209,15 @@ BEGIN
UPDATE pricequote.queue q
SET
tprice_last = tp2.price
FROM pricequote.target_prices_base tp2
FROM pricequote.target_prices tp2
WHERE
q.last_dataseg IS NOT NULL
AND tp2.stlc = q.stlc
AND tp2.ds = q.last_dataseg
AND tp2.chan = q.chan
AND tp2.tier = q.tier
AND FLOOR(q.last_qty / NULLIF(q.pltq, 0))::INT <@ tp2.vol;
-- 1:26 0:08
AND FLOOR(q.vol / NULLIF(q.pltq, 0))::INT <@ tp2.vol;
-- 17 sec
--------------------------------------------------------------------
-- 7) Cost data for requested v1ds and last_dataseg
@ -253,7 +250,7 @@ BEGIN
ON v0l.stlc = q2.stlc AND v0l.v0ds = q2.last_v0ds
) AS s
WHERE q.ctid = s.ctid;
-- 4:15 0:25
-- 28 seconds
--------------------------------------------------------------------
-- 8) List price (lowest valid); allow open-ended ranges (vb_to IS NULL)
@ -283,7 +280,7 @@ BEGIN
listcode = p.jcplcd
FROM best_price p
WHERE q.ctid = p.ctid;
-- 2:48 0:18
-- 18 seconds
--------------------------------------------------------------------
-- 9) Normalize last (when last_dataseg != v1ds) + effective list flags
@ -300,7 +297,7 @@ BEGIN
AND q.curstd_last IS NOT NULL
AND q.curstd IS NOT NULL
AND q.curstd_last <> 0
THEN ROUND(q.curstd / q.curstd_last,5)
THEN q.curstd / q.curstd_last
END,
last_premium_method = CASE
WHEN q.last_isdiff IS NOT NULL
@ -331,7 +328,7 @@ BEGIN
END,
listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE q.listprice END,
list_relevance = CASE WHEN q.customized <> '' THEN 'Ignore - Customized' ELSE '' END;
-- 2:22 0:23
-- 21 seconds
--------------------------------------------------------------------
-- 10) Guidance using normalized last + effective list
@ -339,35 +336,22 @@ BEGIN
UPDATE pricequote.queue q
SET
guidance_price = s.guidance_price,
guidance_reason = s.guidance_reason,
approval_price = s.approval_price,
approval_reason = s.approval_reason
guidance_reason = s.guidance_reason
FROM (
SELECT
q2.ctid,
g.guidance_price,
g.guidance_reason,
a.approval_price,
a.approval_reason
FROM
pricequote.queue q2
g.guidance_reason
FROM pricequote.queue q2
JOIN LATERAL pricequote.guidance_logic(
q2.tprice,
q2.last_price_norm,
q2.listprice_eff,
q2.last_date,
1.0, 1.0, 1.0
q2.last_date
) g ON TRUE
JOIN LATERAL pricequote.approval_logic(
q2.tprice,
q2.last_price_norm,
q2.listprice_eff,
q2.last_date,
1.0, 1.0, 1.0
) a ON TRUE
) s
WHERE q.ctid = s.ctid;
-- 4:33 0:39
-- 31 seconds
--------------------------------------------------------------------
-- 11) Build expl and ui_json identical to single_price_call
@ -422,9 +406,7 @@ BEGIN
'list_relevance', q.list_relevance
),
'guidance_price', q.guidance_price,
'guidance_reason', q.guidance_reason,
'approval_price', q.approval_price,
'approval_reason', q.approval_reason
'guidance_reason', q.guidance_reason
),
ui_json = jsonb_build_object(
'details', jsonb_build_array(
@ -515,7 +497,7 @@ BEGIN
),
'data', q.expl
);
-- 7:59 2:17
-- 2 minutes 33 seconds
--------------------------------------------------------------------
-- 12) Merge back into matrix (store both expl and ui)
@ -539,7 +521,7 @@ BEGIN
AND o.version IN ('Actual', 'Forecast', 'Quotes')
AND o.part IS NOT NULL
AND SUBSTRING(o.glec, 1, 1) <= '2';
-- 14:13 10:09
-- 9 minutes 35 seconds
RAISE NOTICE 'Queue processing complete.';
END;

View File

@ -136,8 +136,6 @@ BEGIN
------------step 6 compute guidance------------
guidance_price NUMERIC(20,5),
guidance_reason NVARCHAR(MAX),
approval_price NUMERIC(20,5),
approval_reason NVARCHAR(MAX),
------------step 7 build json------------------
expl NVARCHAR(MAX),
ui_json NVARCHAR(MAX)
@ -175,7 +173,7 @@ BEGIN
WHEN 'DIS' THEN bc.dba
ELSE sc.dba
END
ELSE bc.dba
ELSE q.bill
END,
pltq = i.mpck,
plevel =
@ -200,8 +198,8 @@ BEGIN
calculated_pallets = FLOOR(q.vol / NULLIF(i.mpck, 0)),
exact_pallets = CAST(ROUND(q.vol / NULLIF(i.mpck, 0), 5) AS NUMERIC(20,5))
FROM @queue q
LEFT JOIN pricing.cust bc ON bc.code = q.bill
LEFT JOIN pricing.cust sc ON sc.code = q.ship
LEFT JOIN rlarp.cust bc ON bc.code = q.bill
LEFT JOIN rlarp.cust sc ON sc.code = q.ship
LEFT JOIN CMSInterfaceIn.[CMS.CUSLG].itemm i ON i.item = q.part;
@ -279,16 +277,16 @@ BEGIN
,curstd_last = CASE WHEN last_isdiff = '' THEN q.curstd_orig ELSE COALESCE(v1l.curstdus, v0l.curstdus) END
,futstd_last = CASE WHEN last_isdiff = '' THEN q.futstd_orig ELSE COALESCE(v1l.futstdus, v0l.futstdus) END
FROM @queue q
LEFT JOIN pricing.cost_v1ds v1 ON
LEFT JOIN rlarp.cost_v1ds v1 ON
v1.stlc = q.stlc
AND v1.v1ds = q.v1ds
LEFT JOIN pricing.cost_v0ds v0 ON
LEFT JOIN rlarp.cost_v0ds v0 ON
v0.stlc = q.stlc
AND v0.v0ds = q.v0ds
LEFT JOIN pricing.cost_v1ds v1l ON
LEFT JOIN rlarp.cost_v1ds v1l ON
v1l.stlc = q.stlc
AND v1l.v1ds = q.last_dataseg
LEFT JOIN pricing.cost_v0ds v0l ON
LEFT JOIN rlarp.cost_v0ds v0l ON
v0l.stlc = q.stlc
AND v0l.v0ds = q.last_v0ds;
@ -358,7 +356,7 @@ BEGIN
SET
listcode = rp.jcplcd
,listprice = rp.price
,listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE rp.price END
,listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE q.listprice END
,list_relevance = CASE WHEN q.customized <> '' THEN 'Ignore - Customized' ELSE '' END
,list_from = vb_from
FROM @queue q
@ -379,33 +377,13 @@ BEGIN
SET
guidance_price = g.guidance_price
,guidance_reason = g.guidance_reason
,approval_price = a.approval_price
,approval_reason = a.approval_reason
FROM @queue q
CROSS APPLY pricing.guidance_logic(
TRY_CAST(q.tprice AS NUMERIC(20,5)),
TRY_CAST(q.last_price_norm AS NUMERIC(20,5)),
TRY_CAST(q.listprice_eff AS NUMERIC(20,5)),
TRY_CAST(q.last_date AS DATE),
--allowable price drop percent
1.0,
--cap on last price
1.0,
--cap on list percent
1.0
) g
CROSS APPLY pricing.approval_logic(
TRY_CAST(q.tprice AS NUMERIC(20,5)),
TRY_CAST(q.last_price_norm AS NUMERIC(20,5)),
TRY_CAST(q.listprice_eff AS NUMERIC(20,5)),
TRY_CAST(q.last_date AS DATE),
--allowable price drop percent
1.0,
--cap on last price
1.0,
--cap on list percent
1.0
) a;
TRY_CAST(q.last_date AS DATE)
) g;
--------------------------------------------------------------------------------
-- Step 8: Assemble structured 'expl' JSON from populated columns.
@ -452,8 +430,6 @@ BEGIN
,q.list_relevance AS list_relevance
,q.guidance_price AS guidance_price
,q.guidance_reason AS guidance_reason
,q.approval_price AS approval_price
,q.approval_reason AS approval_reason
-- JSON_QUERY(hist) AS [history]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
@ -468,28 +444,11 @@ BEGIN
(
SELECT
panel.label,
panel.detailLevel,
JSON_QUERY(panel.details) AS details
FROM (
-- Cost
SELECT
'Cost' AS label,
5 AS detailLevel,
(
SELECT
'Current Std' AS label,
5 AS detailLevel,
COALESCE(q.curstd,0) AS value,
'currency' AS type,
'' AS note
FOR JSON PATH
) AS details
UNION ALL
-- History Panel
SELECT
'History' AS label,
1 as detailLevel,
(
SELECT
----------------------label------------------------------------------------
@ -499,14 +458,12 @@ BEGIN
CASE ISNULL(q.last_source, '')
WHEN 'mrq' THEN 'Similar Quote'
WHEN 'mrs' THEN 'Similar Sale'
WHEN 'dsq' THEN 'Last Quote'
WHEN 'dss' THEN 'Last Sale'
WHEN 'dsq' THEN 'Last Sale'
WHEN 'dss' THEN 'Last Quote'
ELSE ''
END
ELSE 'No Recent'
END AS label,
----------------------detail-----------------------------------------------
1 AS detailLevel,
----------------------value------------------------------------------------
ISNULL(q.last_price, 0) AS value,
----------------------type-------------------------------------------------
@ -528,12 +485,8 @@ BEGIN
END,
ISNULL(' | ' + CONVERT(varchar(10), q.last_date, 120), ''),
' | Qty ' + format(q.last_qty,'#,###'),
CASE WHEN COALESCE(last_isdiff,'') <> ''
THEN
' | Normalized To: ' + cast(last_price_norm AS varchar(10))
+ ' | ' /*+ q.last_premium_method*/ + ' Last Target = ' + format(q.tprice_last,'0.0####') + ' | Current Target = ' + format(q.tprice,'0.0####')
ELSE ''
END
CASE WHEN last_isdiff <> '' THEN ' | Normalized To: ' + cast(last_price_norm AS varchar(10)) ELSE '' END,
' | ' /*+ q.last_premium_method*/ + ' Last Target = ' + format(q.tprice_last,'0.0####') + ' | Current Target ' + format(q.tprice,'0.0####')
)
ELSE
''
@ -547,30 +500,25 @@ BEGIN
-- List Panel
SELECT
'List' AS label,
1 AS detailLevel,
(
SELECT
COALESCE('Code: ' + q.listcode,'No List') AS label,
1 AS detailLevel,
COALESCE(q.listprice,0) AS value,
'currency' AS type,
COALESCE('List Min Qty: ' + format(q.list_from,'#,###'),'') + CASE WHEN q.list_relevance = '' THEN '' ELSE ' (' + q.list_relevance + ')' END AS note
FOR JSON PATH
) AS details
)
UNION ALL
-- Target Support Panel
SELECT
'Target Calculation' AS label,
5 AS detailLevel,
(
SELECT * FROM (
SELECT
----------------------label------------------------------------------------
CASE WHEN value <> '' THEN replace(RTRIM(SUBSTRING(value,1,18)),'Anchor:', '') ELSE 'No Target' END AS label,
----------------------detailLevel------------------------------------------
10 AS detailLevel,
----------------------value------------------------------------------------
CASE WHEN value <> '' THEN
TRY_CAST(SUBSTRING(value,23,7) AS NUMERIC(20,5))
@ -595,8 +543,6 @@ BEGIN
SELECT
----------------------label------------------------------------------------
'Target' AS label,
----------------------detailLevel------------------------------------------
5 AS detailLevel,
----------------------value------------------------------------------------
tprice AS value,
----------------------type-------------------------------------------------
@ -613,11 +559,9 @@ BEGIN
-- Guidance Panel
SELECT
'Guidance' AS label,
1 AS detailLevel,
(
SELECT
'Price' AS label,
1 AS detailLevel,
COALESCE(q.guidance_price,0) AS value,
'currency' AS type,
q.guidance_reason AS note

View File

@ -35,7 +35,7 @@
====================================================================================
*/
-- DROP FUNCTION pricequote.single_price_call(text,text,text,text,numeric) CASCADE;
--DROP FUNCTION pricequote.single_price_call(text,text,text,text,numeric) CASCADE;
CREATE OR REPLACE FUNCTION pricequote.single_price_call(
_bill TEXT,
@ -89,8 +89,6 @@ RETURNS TABLE (
list_relevance TEXT,
guidance_price NUMERIC,
guidance_reason TEXT,
approval_price NUMERIC,
approval_reason TEXT,
expl JSONB,
ui_json JSONB
) AS $$
@ -150,8 +148,6 @@ DECLARE
------------step 6 compute guidance------------
_guidance_price NUMERIC;
_guidance_reason TEXT;
_approval_price NUMERIC;
_approval_reason TEXT;
------------step 7 build json------------------
_expl JSONB := '{}'::jsonb;
_ui_json JSONB := '{}'::jsonb;
@ -269,7 +265,7 @@ BEGIN
,_tmath
,_volume_range
FROM
pricequote.target_prices_base tp
pricequote.target_prices tp
WHERE
tp.stlc = _stlc
AND tp.ds = _v1ds
@ -287,36 +283,38 @@ BEGIN
INTO
_tprice_last
FROM
pricequote.target_prices_base tp
pricequote.target_prices tp
WHERE
tp.stlc = _stlc
AND tp.ds = _last_dataseg
AND tp.chan = _chan
AND tp.tier = _tier
AND FLOOR(_last_qty / NULLIF(_pltq, 0))::int <@ tp.vol;
AND FLOOR(_vol / NULLIF(_pltq, 0))::int <@ tp.vol;
------------------------------------------------------------------
-- Step 4: Cost data for normalization
------------------------------------------------------------------
-- Current/future standard for requested v1ds
SELECT
ROUND(CASE WHEN COALESCE(_customized,'') = '' THEN _curstd_orig ELSE COALESCE(v1.curstdus, v0.curstdus) END,5) AS curstd,
ROUND(CASE WHEN COALESCE(_customized,'') = '' THEN _futstd_orig ELSE COALESCE(v1.futstdus, v0.futstdus) END,5) AS futstd,
ROUND(CASE WHEN COALESCE(_last_isdiff,'') = '' THEN _curstd_orig ELSE COALESCE(v1l.curstdus, v0l.curstdus) END,5) AS curstd_last,
ROUND(CASE WHEN COALESCE(_last_isdiff,'') = '' THEN _futstd_orig ELSE COALESCE(v1l.futstdus, v0l.futstdus) END,5) AS futstd_last
curstdus, futstdus
INTO
_curstd, _futstd, _curstd_last, _futstd_last
FROM (VALUES (1)) AS x(dummy)
LEFT JOIN rlarp.cost_v1ds v1
ON v1.stlc = _stlc AND v1.v1ds = _v1ds
LEFT JOIN rlarp.cost_v0ds v0
ON v0.stlc = _stlc AND v0.v0ds = _v0ds
LEFT JOIN rlarp.cost_v1ds v1l
ON v1l.stlc = _stlc AND v1l.v1ds = _last_dataseg
LEFT JOIN rlarp.cost_v0ds v0l
ON v0l.stlc = _stlc AND v0l.v0ds = _last_v0ds
LIMIT 1;
_curstd, _futstd
FROM
"CMS.CUSLG".itemm i
WHERE
i.item = _part
AND i.v1ds = _v1ds;
-- Current/future standard for last_dataseg
SELECT
curstdus, futstdus
INTO
_curstd_last, _futstd_last
FROM
"CMS.CUSLG".itemm i
WHERE
i.item = _part
AND i.v1ds = _last_dataseg;
------------------------------------------------------------------
-- Step 5: Normalize last price if needed
@ -327,7 +325,7 @@ BEGIN
_last_price_norm := ROUND(_last_price * (_tprice / _tprice_last), 5);
_last_premium_method := 'Target Price Ratio';
ELSIF _curstd_last IS NOT NULL AND _curstd IS NOT NULL AND _curstd_last <> 0 THEN
_last_premium := ROUND(_curstd / _curstd_last, 5);
_last_premium := _curstd / _curstd_last;
_last_price_norm := ROUND(_last_price * (_curstd / _curstd_last), 5);
_last_premium_method := 'Cost Ratio';
ELSE
@ -376,16 +374,10 @@ BEGIN
SELECT
gl.guidance_price
,gl.guidance_reason
,al.approval_price
,al.approval_reason
INTO
_guidance_price
,_guidance_reason
,_approval_price
,_approval_reason
FROM
pricequote.guidance_logic(_tprice, _last_price_norm, _listprice_eff, _last_date, 1.0, 1.0, 1.0) gl
CROSS JOIN pricequote.approval_logic(_tprice, _last_price_norm, _listprice_eff, _last_date, 1.0, 1.0, 1.0) al;
FROM pricequote.guidance_logic(_tprice, _last_price_norm, _listprice_eff, _last_date) gl;
------------------------------------------------------------------
-- Step 8: Build explanation JSON
@ -433,8 +425,7 @@ BEGIN
'targets',
jsonb_build_object(
'target_price', _tprice,
'target_math', _tmath,
'volume_range', _volume_range
'target_math', _tmath
),
'list',
jsonb_build_object(
@ -444,9 +435,7 @@ BEGIN
'list_relevance', _list_relevance
),
'guidance_price', _guidance_price,
'guidance_reason', _guidance_reason,
'approval_price', _approval_price,
'approval_reason', _approval_reason
'guidance_reason', _guidance_reason
);
------------------------------------------------------------------
@ -459,11 +448,9 @@ BEGIN
------------------------------------------
jsonb_build_object(
'label', 'History',
10, 'detailLevel',
'details', jsonb_build_array(
jsonb_build_object(
'label', CASE WHEN _last_price IS NOT NULL THEN 'Last Sale: ' || _last_date ELSE 'No Recent' END,
'detailLevel', 10,
'value', COALESCE(_last_price,0),
'type', 'currency',
'note', CASE WHEN _last_price IS NOT NULL THEN
@ -481,7 +468,6 @@ BEGIN
||CASE WHEN COALESCE(_last_premium,1) <> 1 THEN
COALESCE(jsonb_build_array(jsonb_build_object(
'label','Price Difference',
'detailLevel',10,
'value', _last_premium,
'type','percent',
'note', _last_premium_method
@ -492,7 +478,6 @@ BEGIN
||CASE WHEN COALESCE(_last_premium,1) <> 1 THEN
COALESCE(jsonb_build_array(jsonb_build_object(
'label','Adjusted Price',
'detailLevel',10,
'value', _last_price_norm,
'type','currency',
'note','normalized to ' || _v1ds
@ -506,11 +491,9 @@ BEGIN
------------------------------------------
jsonb_build_object(
'label', 'List',
'detailLevel',10,
'details', jsonb_build_array(
jsonb_build_object(
'label', 'List:' || COALESCE(_list_code, ''),
'detailLevel',10,
'value', _list_price,
'type', 'currency',
'note', _list_relevance
@ -522,7 +505,6 @@ BEGIN
------------------------------------------
jsonb_build_object(
'label', 'Target Calculation',
'detailLevel',10,
'details',
-- jsonb_build_array(
(
@ -531,8 +513,6 @@ BEGIN
jsonb_build_object(
----------------------label------------------------------------------------
'label',CASE WHEN value <> '' THEN RTRIM(SUBSTRING(value,1,18)) ELSE 'No Target' END,
----------------------detailLevel------------------------------------------
'detailLevel',10,
----------------------value------------------------------------------------
'value',CASE WHEN value <> '' THEN
SUBSTRING(value,23,7)::NUMERIC(20,5) +
@ -566,11 +546,9 @@ BEGIN
------------------------------------------
jsonb_build_object(
'label', 'Guidance',
'detailLevel',10,
'details', jsonb_build_array(
jsonb_build_object(
'label', 'Price',
'detailLevel',10,
'value', COALESCE(_guidance_price,0),
'type', 'currency',
'note', COALESCE(_guidance_reason,'')
@ -594,7 +572,6 @@ BEGIN
_tprice, _tmath, _volume_range,
_list_price, _list_code, _listprice_eff, _list_relevance,
_guidance_price, _guidance_reason,
_approval_price, _approval_reason,
_expl, _ui_json;
END;
$$ LANGUAGE plpgsql;

View File

@ -105,9 +105,6 @@ lq AS MATERIALIZED (
-- ,jsonb_pretty(pricing) pricing
,p.guidance_price
,p.guidance_reason
,p.approval_price
,p.approval_reason
,p.tprice guidance_target
,jsonb_pretty(p.ui_json->'data') expl
FROM
lq
@ -178,4 +175,4 @@ lq AS MATERIALIZED (
WHERE
COALESCE(g.bestprice,1) = 1
)
SELECT * FROM hist --LIMIT 1000--WHERE qid = 108655
SELECT * FROM hist --WHERE qid = 108655

View File

@ -1,275 +0,0 @@
CREATE OR ALTER PROCEDURE pricing.rebuild_lastprice
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- auto-rollback on most errors
BEGIN TRY
BEGIN TRAN; -- start transaction
--------------------------------------------------------------------------------
-- Reset target tables
--------------------------------------------------------------------------------
--DROP TABLE IF EXISTS pricing.lastpricedetail;
DELETE FROM pricing.lastpricedetail;
DROP TABLE IF EXISTS #flagged;
--------------------------------------------------------------------------------
-- Stage 1: Load cleaned input rows
-- Filters out irrelevant quotes/orders and calculates unit prices
--------------------------------------------------------------------------------
WITH base AS (
SELECT
o."Customer" AS customer,
o."Part Group" AS partgroup,
RTRIM(i.V1DS) AS dataseg,
o."Data Source" AS version,
o."Part Code" AS part,
o."Units" AS qty,
CASE
WHEN o."Units" = 0 THEN NULL
ELSE ROUND(o.[Value USD] / NULLIF(o."Units", 0), 5)
END AS price,
o.[Order Date] AS odate,
o.[Order Number] AS ordnum,
o.[Quote Number] AS quoten
FROM
fanalysis.rlarp.osm_stack_pretty o
INNER JOIN CMSInterfaceIn.[CMS.CUSLG].ITEMM i
ON i.item = o.[Part Code]
WHERE
o.[Data Source] IN ('Actual', 'Quotes')
AND o."Customer" IS NOT NULL
AND o."Financial Statement Line" = '41010'
AND o."Order Status" <> 'CANCELLED'
AND o."Units" > 0
AND o."Part Group" <> ''
-- Optional filter for testing
-- AND o."Customer" = 'ESBENSHADES GREENHOUSE'
),
--------------------------------------------------------------------------------
-- Stage 2: Rank each row based on recency and volume rules
-- Flags include:
-- - rn_mrs: most recent sale
-- - rn_mrq: most recent quote
-- - rn_lvs: largest sale in last year
-- - rn_lvq: largest quote in last year
-- - rn_dss: most recent sale per dataseg
-- - rn_dsq: most recent quote per dataseg
--------------------------------------------------------------------------------
ranked AS (
SELECT
b.*
-- Most recent sale (Actuals only)
,CASE WHEN b.version = 'Actual' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY b.odate DESC
)
END AS rn_mrs
-- Most recent quote (Quotes only)
,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY b.odate DESC
)
END AS rn_mrq
-- Largest volume sale (Actuals only; last 12 months prioritized)
,CASE WHEN b.version = 'Actual' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY
CASE WHEN b.version = 'Actual' AND b.odate >= DATEADD(YEAR, -1, GETDATE()) THEN 1 ELSE 0 END DESC,
b.qty DESC
)
END AS rn_lvs
-- Largest volume quote (Quotes only; last 12 months prioritized)
,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY
CASE WHEN b.version = 'Quotes' AND b.odate >= DATEADD(YEAR, -1, GETDATE()) THEN 1 ELSE 0 END DESC,
b.qty DESC
)
END AS rn_lvq
,CASE WHEN b.version = 'Actual' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY b.odate DESC
)
END AS rn_dss
,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY b.odate DESC
)
END AS rn_dsq
FROM base b
)
--------------------------------------------------------------------------------
-- Stage 2.5: Save only rows that meet any of the above criteria
-- and annotate each with global-level flag (mrs, mrq, lvs, lvq)
--------------------------------------------------------------------------------
SELECT
*,
CASE WHEN rn_mrs = 1 THEN 'mrs' END AS f1,
CASE WHEN rn_mrq = 1 THEN 'mrq' END AS f2,
CASE WHEN rn_lvs = 1 THEN 'lvs' END AS f3,
CASE WHEN rn_lvq = 1 THEN 'lvq' END AS f4,
CASE WHEN rn_dss = 1 AND version = 'Actual' THEN 'dss' END AS f5,
CASE WHEN rn_dsq = 1 AND version = 'Quotes' THEN 'dsq' END AS f6
INTO #flagged
FROM ranked
WHERE
rn_mrs = 1
OR rn_mrq = 1
OR rn_lvs = 1
OR rn_lvq = 1
OR (rn_dss = 1 AND version = 'Actual')
OR (rn_dsq = 1 AND version = 'Quotes');
CREATE NONCLUSTERED INDEX ix_flagged_lookup
ON #flagged(customer, partgroup, dataseg, version, part, qty, price, odate, ordnum, quoten);
--------------------------------------------------------------------------------
-- Stage 3: Build JSON from flagged rows
--------------------------------------------------------------------------------
-- Step 3.1: Explode all flags from the #flagged table
WITH exploded_flags AS (
SELECT
customer, partgroup, part, dataseg, version, qty, price, odate, ordnum, quoten,
flag
FROM #flagged
CROSS APPLY (VALUES (f1), (f2), (f3), (f4), (f5), (f6)) AS f(flag)
WHERE flag IS NOT NULL
)
--SELECT * FROM exploded_flags
-- Step 3.2: Serialize each row into its JSON snippet
-- Carry odate and version for deduplication in seg_pieces
,serialized_flags AS (
SELECT
customer,
partgroup,
dataseg,
flag,
odate,
version,
CONCAT(
'"', flag, '":',
JSON_QUERY((
SELECT
version,
dataseg AS datasegment,
part,
qty,
price,
odate,
ordnum,
quoten,
flag
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
))
) AS json_piece
FROM exploded_flags
)
--SELECT * FROM serialized_flags
-- Step 3.3: Collect all global-level flags (mrs, mrq, lvs, lvq)
,flag_json AS (
SELECT
customer,
partgroup,
STRING_AGG(json_piece, ',') AS json_block
FROM serialized_flags
WHERE flag IN ('mrs', 'mrq', 'lvs', 'lvq')
GROUP BY customer, partgroup
)
--SELECT * FROM flag_json
-- Step 3.4: Nest dss/dsq under each dataseg
-- Only keep the most recent dss/dsq per dataseg/version (prevents duplicate keys)
,seg_pieces AS (
SELECT
customer,
partgroup,
dataseg,
STRING_AGG(json_piece, ',') AS inner_json
FROM (
SELECT sf.*
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, flag
ORDER BY odate DESC,
CASE WHEN version = 'Actual' THEN 1 ELSE 0 END DESC
) AS rn
FROM serialized_flags
WHERE flag IN ('dss', 'dsq')
) sf
WHERE sf.rn = 1
) deduped
GROUP BY customer, partgroup, dataseg
)
--SELECT * FROM seg_pieces
-- Step 3.5: Wrap the inner_json under dataseg key
,wrapped_segs AS (
SELECT
customer,
partgroup,
CONCAT(
'"', dataseg, '": {', inner_json, '}'
) AS json_piece
FROM seg_pieces
)
-- Step 3.6: Aggregate all dataseg entries into one JSON block per customer/partgroup
,seg_json AS (
SELECT
customer,
partgroup,
STRING_AGG(json_piece, ',') AS json_block
FROM wrapped_segs
GROUP BY customer, partgroup
)
--SELECT * FROM seg_json
--------------------------------------------------------------------------------
-- Stage 4: Merge flags and segment blocks into a single JSON object
-- Write final pricing history to pricing.lastpricedetail
--------------------------------------------------------------------------------
INSERT INTO
pricing.lastpricedetail
SELECT
COALESCE(f.customer, s.customer) AS customer,
COALESCE(f.partgroup, s.partgroup) AS partgroup,
CONCAT(
'{',
COALESCE(f.json_block, ''),
CASE
WHEN f.json_block IS NOT NULL AND s.json_block IS NOT NULL THEN ','
ELSE ''
END,
COALESCE(s.json_block, ''),
'}'
) AS part_stats
FROM flag_json f
FULL OUTER JOIN seg_json s
ON f.customer = s.customer AND f.partgroup = s.partgroup;
--------------------------------------------------------------------------------
-- Commit if everything succeeded
--------------------------------------------------------------------------------
COMMIT TRAN;
END TRY
BEGIN CATCH
-- Ensure transaction is rolled back
IF XACT_STATE() <> 0
BEGIN
ROLLBACK TRAN;
END
-- Optional: cleanup temp table (temp table persists for session until proc end,
-- but explicit drop is helpful if you reuse names or want to free resources)
IF OBJECT_ID('tempdb..#flagged') IS NOT NULL
DROP TABLE #flagged;
-- Rethrow the original error to the caller
THROW;
END CATCH;
END;

View File

@ -1 +0,0 @@
REFRESH MATERIALIZED VIEW pricequote.lastpricedetail;

View File

@ -1,6 +0,0 @@
EXEC pricing.pricing.rebuild_lastprice;
--2:45
EXEC pricing.pricing.rebuild_pricelist;
--14 secconds
EXEC pricing.pricing.rebuild_targets;
--12:49

View File

@ -1,6 +0,0 @@
REFRESH MATERIALIZED VIEW pricequote.lastpricedetail;
--37 seconds
CALL pricequote.rebuild_pricelist();
--32 seconds
CALL pricequote.refresh_target_prices_base();
--45 seconds

View File

@ -1,226 +0,0 @@
CREATE OR ALTER PROCEDURE pricing.rebuild_pricelist
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- auto-rollback on most errors
BEGIN TRY
BEGIN TRAN;
DROP TABLE pricing.pricelist_ranged;
CREATE TABLE pricing.pricelist_ranged (
jcplcd varchar(5) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcpart varchar(20) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcunit varchar(3) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcvoll numeric(12,5) NOT NULL,
jcpric numeric(12,5) NOT NULL,
vb_from float NULL,
vb_to float NULL,
price float NOT NULL
);
CREATE NONCLUSTERED INDEX pricelist_ranged_idx ON PRICING.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ) ;
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----------------------------------------------------------traverse unit of measure graph-----------------------------------------------------------------------
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-------------setup table to hold target conversions---------------------
SELECT DISTINCT
jcpart partn
,jcunit fu
,'PC' tu
,cast(null as numeric) factor
INTO
#anchor
FROM
cmsinterfacein.lgdat.iprcc
WHERE
1=1;
--SELECT * FROM #anchor
-------pre-build punit stacked on itself with the columns flipped so you can go either direction per join---------
SELECT
*
INTO
#g
FROM
(
SELECT
IHPART IHPART,
rtrim(IHUNT1) IHUNT1,
rtrim(IHUNT2) IHUNT2,
IHCNV1 IHCNV1,
IHCNV2 IHCNV2
FROM
CMSInterfaceIN.LGDAT.PUNIT pu
--only deal with parts in the anchor table or the &&global parts
INNER JOIN (
SELECT DISTINCT partn FROM #anchor
) items ON
items.partn = pu.ihpart
OR pu.ihpart = '&&GLOBAL'
UNION
SELECT
IHPART IHPART,
rtrim(IHUNT2) IHUNT1,
rtrim(IHUNT1) IHUNT2,
IHCNV2 IHCNV1,
IHCNV1 IHCNV2
FROM
CMSInterfaceIN.LGDAT.PUNIT pu
--only deal with parts in the anchor table or the &&global parts
INNER JOIN (
SELECT DISTINCT partn FROM #anchor
) items ON
items.partn = pu.ihpart
OR pu.ihpart = '&&GLOBAL'
) x ;
CREATE INDEX g_idx on #g(ihpart,ihunt1);
WITH
--------do the expansion on all paths until the target uom is matched----------------------------------------------
--(complains about types not matching between anchor and recursion, explicitly just casting everything)
uom (partn, partx, lvl, mastf, mastt, xf, xt, factor, xfactor, xnum, xden, id, uom_list) AS
(
SELECT
cast(partn as varchar(20)) --partn
,cast(partn as varchar(20)) --partx
,cast(0 as int) --lvl
,fu --mastf
,tu --mastt
,cast(fu as varchar(3)) --xf
,cast(fu as varchar(3)) --xt
,CAST(1 AS FLOAT) --factor
,CAST(1 AS FLOAT) --xfactor
,CAST(1 AS FLOAT) --xnum
,CAST(1 AS FLOAT) --xden
,format(row_number() over (ORDER BY partn),'000000')
,cast(trim(fu) as varchar(max))
FROM
#anchor
UNION ALL
SELECT
cast(uom.partn as varchar(20)) --partn
,cast(ihpart as varchar(20)) --partx
,CAST(uom.lvl + 1 AS INT) --lvl
,uom.mastf --mastf
,uom.mastt --mastt
,cast(p.ihunt1 as varchar(3)) --xf
,cast(p.ihunt2 as varchar(3)) --xt
,CAST(p.ihcnv2/p.ihcnv1 AS FLOAT) --factor
,CAST(p.ihcnv2/p.ihcnv1 AS FLOAT) * uom.xfactor --xfactor
,p.ihcnv2 * uom.xnum --xnum
,p.ihcnv1 * uom.xden --xden
,uom.id + '.' + format(row_number() over (PARTITION BY uom.id ORDER BY partn),'00')
,uom.uom_list + '.' + trim(p.ihunt2)
FROM
uom
INNER JOIN #g p ON
p.ihpart IN (uom.partn,'&&GLOBAL')
AND p.ihunt1 = uom.xt
WHERE
1=1
--AND p.ihunt2 not in ('BD','BG','BU','BX','CA','CS','PA','PL','SL','C','K','DOZ','PR')
AND p.ihunt1 <> uom.mastt
--prevent recursion: newest joined UOM can't be in the history
AND charindex(p.ihunt2,uom.uom_list) = 0
)
--SELECT COUNT(*) FROM UOM
--------------uom is going to have multiple rows per requested conversion, need to use row_number to pick the best row------------------------------
,sorted AS (
SELECT
partn, mastf from_uom, xt to_uom, xfactor factor, lvl steps, row_number() OVER (PARTITION BY partn, mastf, mastt ORDER BY lvl ASC, factor ASC) rn
FROM
uom
WHERE
xt = mastt
)
SELECT * INTO #uom FROM sorted WHERE rn = 1;
--so far so good
drop table #anchor;
drop table #g;
TRUNCATE TABLE pricing.pricelist_ranged;
WITH
conv AS (
SELECT
p.jcplcd,
p.jcpart,
p.jcunit,
p.jcvoll,
p.jcpric,
u.factor,
-- Normalize volume and price to PC
p.jcvoll * u.factor AS vol_pc,
p.jcpric / u.factor AS price_pc
FROM
cmsinterfacein.lgdat.iprcc p
INNER JOIN #uom u
ON u.partn = p.jcpart
AND u.from_uom = p.jcunit
AND u.to_uom = 'PC'
),
sorted AS (
SELECT
c.*,
ROW_NUMBER() OVER (PARTITION BY c.jcplcd, c.jcpart ORDER BY vol_pc ASC) AS rn
FROM conv c
),
ranged AS (
SELECT
curr.jcplcd,
curr.jcpart,
curr.jcunit,
curr.jcvoll,
curr.jcpric,
curr.vol_pc,
curr.price_pc,
curr.vol_pc AS vb_from,
COALESCE(next.vol_pc, 9999999.0) AS vb_to
FROM
sorted curr
LEFT JOIN sorted next
ON curr.jcplcd = next.jcplcd
AND curr.jcpart = next.jcpart
AND curr.rn + 1 = next.rn
)
INSERT INTO
pricing.pricelist_ranged
SELECT
RTRIM(jcplcd) jcplcd,
RTRIM(jcpart) jcpart,
jcunit,
jcvoll,
jcpric,
vb_from,
vb_to,
price_pc AS price
FROM
ranged;
--CREATE INDEX pricelist_ranged_idx ON pricing.pricelist_ranged(jcpart, jcplcd, vb_from, vb_to);
COMMIT TRAN;
END TRY
BEGIN CATCH
-- Ensure transaction is rolled back
IF XACT_STATE() <> 0
BEGIN
ROLLBACK TRAN;
END
IF OBJECT_ID('tempdb..#anchor') IS NOT NULL DROP TABLE #anchor;
IF OBJECT_ID('tempdb..#g') IS NOT NULL DROP TABLE #g;
IF OBJECT_ID('tempdb..#uom') IS NOT NULL DROP TABLE #uom;
-- Re-throw original error
THROW;
END CATCH;
END;

View File

@ -1,108 +0,0 @@
CREATE OR REPLACE PROCEDURE pricequote.rebuild_pricelist()
LANGUAGE plpgsql
AS $$
BEGIN
DROP TABLE IF EXISTS uomc;
CREATE TEMP TABLE uomc AS (
WITH
uom AS (
SELECT
uom.p part
,uom.f fu
,uom.t tu
,uom.nm/uom.dm conv
FROM
(
SELECT
jsonb_agg(row_to_json(d)::jsonb) jdoc
FROM
(
select distinct
jcpart partn
, jcunit fu
, 'PC' tu
from
lgdat.iprcc
WHERE
jcpart <> ''
) d
) c
JOIN LATERAL rlarp.uom_array(c.jdoc) uom ON TRUE
)
SELECT * FROM uom
) WITH DATA;
CREATE INDEX uom_idx ON uomc (part, fu, tu);
-- Clear the output table
TRUNCATE TABLE pricequote.pricelist_ranged;
--DROP TABLE pricequote.pricelist_ranged;
-- Compute normalized volume/price and ranges
--CREATE TABLE pricequote.pricelist_ranged AS (
WITH
conv AS (
SELECT
p.jcplcd,
p.jcpart,
p.jcunit,
p.jcvoll,
p.jcpric,
u.conv,
(p.jcvoll * u.conv) AS vol_pc,
(p.jcpric / NULLIF(u.conv, 0)) AS price_pc
FROM
lgdat.iprcc p
INNER JOIN uomc u
ON u.part = p.jcpart
AND u.fu = p.jcunit
AND u.tu = 'PC'
),
--SELECT * FROM conv LIMIT 1000
sorted AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY jcplcd, jcpart ORDER BY vol_pc ASC) AS rn
FROM conv
),
ranged AS (
SELECT
curr.jcplcd,
curr.jcpart,
curr.jcunit,
curr.jcvoll,
curr.jcpric,
curr.vol_pc,
curr.price_pc price,
curr.vol_pc AS vb_from,
COALESCE(next.vol_pc, 9999999.0) AS vb_to
FROM
sorted curr
LEFT JOIN sorted next
ON curr.jcplcd = next.jcplcd
AND curr.jcpart = next.jcpart
AND curr.rn + 1 = next.rn
)
--SELECT * FROM ranged
INSERT INTO pricequote.pricelist_ranged (
jcplcd, jcpart, jcunit, jcvoll, jcpric, vb_from, vb_to, price
)
SELECT
jcplcd,
jcpart,
jcunit,
jcvoll,
jcpric,
vb_from,
vb_to,
price
FROM ranged;
END;
$$;
--CREATE INDEX pricelist_ranged_idx ON pricequote.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ) ;

View File

@ -1,42 +0,0 @@
CREATE OR ALTER PROCEDURE pricing.rebuild_targets
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- auto-rollback on most runtime errors
BEGIN TRY
BEGIN TRAN;
DELETE FROM pricing.target_prices;
INSERT INTO
pricing.target_prices
SELECT
stlc,
ds,
chan,
tier,
vol,
-- Extract lower bound: text between '[' and ','
TRY_CAST(SUBSTRING(vol, 2, CHARINDEX(',', vol) - 2) AS INT) AS lower_bound,
-- Extract upper bound: text between ',' and ')'
CASE
WHEN RIGHT(vol, 2) = ',)' THEN NULL
ELSE TRY_CAST(SUBSTRING(vol, CHARINDEX(',', vol) + 1, LEN(vol) - CHARINDEX(',', vol) - 1) AS INT)
END AS upper_bound,
price,
math
FROM
usmidsap02.ubm.pricequote.target_prices_view;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRAN;
-- Rethrow original error
THROW;
END CATCH;
END

View File

@ -1,28 +0,0 @@
CREATE OR REPLACE PROCEDURE pricequote.refresh_target_prices_base()
LANGUAGE plpgsql
AS $$
BEGIN
DELETE FROM pricequote.target_prices_base;
WITH expand AS (
SELECT
c.compset,
c.stlc,
c.floor,
b.ds,
b.chan,
b.tier,
b.vol,
b.val,
b.price,
b.math AS math
FROM pricequote.core_target c
LEFT JOIN LATERAL pricequote.build_pricing_path_base(
c.options || jsonb_build_object('entity','Anchor','attr',c.stlc,'val',c.floor,'func','Price')
) AS b
ON b.lastflag
)
INSERT INTO pricequote.target_prices_base
SELECT * FROM expand;
END;
$$;

View File

@ -1,66 +0,0 @@
CREATE OR ALTER PROCEDURE pricing.sync_external
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- causes runtime errors to auto-rollback
BEGIN TRY
BEGIN TRAN;
-- Refresh pricing.ffcret
TRUNCATE TABLE pricing.ffcret;
INSERT INTO pricing.ffcret
SELECT *
FROM fanalysis.rlarp.ffcret;
-- Refresh pricing.ffterr
TRUNCATE TABLE pricing.ffterr;
INSERT INTO pricing.ffterr
SELECT *
FROM fanalysis.rlarp.ffterr;
-- Refresh pricing.gld
TRUNCATE TABLE pricing.gld;
INSERT INTO pricing.gld
SELECT *
FROM fanalysis.rlarp.gld;
-- Refresh pricing.qrh
TRUNCATE TABLE pricing.qrh;
INSERT INTO pricing.qrh
SELECT *
FROM fanalysis.rlarp.qrh;
-- Refresh pricing.sach
TRUNCATE TABLE pricing.sach;
INSERT INTO pricing.sach
SELECT *
FROM fanalysis.lgdat.sach;
COMMIT TRAN;
-- rebuild last price
EXEC pricing.rebuild_lastprice;
-- rebuild ranged price list
EXEC pricing.rebuild_pricelist;
-- rebuild target prices
EXEC pricing.rebuild_targets;
END TRY
BEGIN CATCH
-- Rollback if any error
IF XACT_STATE() <> 0
ROLLBACK TRAN;
-- rethrow original error with context
DECLARE @ErrMsg NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE @ErrSec NVARCHAR(200) = ERROR_SEVERITY();
DECLARE @ErrState INT = ERROR_STATE();
RAISERROR('refresh_pricing_tables failed: %s', 16, 1, @ErrMsg);
THROW; -- rethrow original error for callers to handle
END CATCH;
END

View File

@ -1,28 +0,0 @@
CREATE OR ALTER VIEW pricing.arcstx AS
SELECT
v6part PART,
v6plnt plnt,
v6stat stat,
v6rpln rpln,
v6unti unit,
COALESCE(cnsdat, cosdat, y3sdat) sdate,
COALESCE(cnstcs,costcs, y3stcs) std,
COALESCE(cnmats,costcs,y3smat + y3soc + y3sshc) mat,
COALESCE(cnlabs,y3slab,0) lab,
COALESCE(cnbrvs,y3svbr,0) var,
COALESCE(cnbrfs,y3sfbr,0) fix,
COALESCE(cnstoc,y3sotc,0) oth
FROM
CMSInterfaceIN.lgdat.stka
LEFT OUTER JOIN CMSInterfaceIN.ARCHIVE.ftcstm_2510 ftcstm ON
cnpart = v6part
AND cnplnt = v6plnt
LEFT OUTER JOIN CMSInterfaceIN.ARCHIVE.ftcstp_2510 ftcstp ON
copart = v6part
AND coplnt = v6plnt
LEFT OUTER JOIN CMSInterfaceIN.ARCHIVE.ftcstr_2510 ftcstr ON
y3part = v6part
AND y3plnt = v6plnt
WHERE
v6plnt IN ('152','154','155','112','113');

View File

@ -1,16 +0,0 @@
DROP TABLE IF EXISTS pricequote.core_target;
CREATE TABLE pricequote.core_target (
compset TEXT NOT NULL,
stlc TEXT NOT NULL,
floor NUMERIC NOT NULL,
options JSONB NOT NULL,
PRIMARY KEY (stlc)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON pricequote.target_prices TO PUBLIC;
DROP TABLE IF EXISTS import.core_target;
CREATE TABLE import.core_target AS (SELECT * FROM pricequote.core_target);

View File

@ -1,85 +0,0 @@
DROP TABLE pricing.cost_v1ds
DROP TABLE pricing.cost_v0ds
-- Final tables (one-time create)
CREATE TABLE pricing.cost_v1ds (
stlc varchar(50) NOT NULL,
v1ds varchar(50) NOT NULL,
curstdus decimal(19,6) NULL,
futstdus decimal(19,6) NULL,
CONSTRAINT PK_cost_v1ds PRIMARY KEY (stlc, v1ds)
);
CREATE INDEX IX_cost_v1ds_cur ON pricing.cost_v1ds(stlc, v1ds, curstdus);
CREATE TABLE pricing.cost_v0ds (
stlc varchar(50) NOT NULL,
v0ds varchar(50) NOT NULL,
curstdus decimal(19,6) NULL,
futstdus decimal(19,6) NULL,
CONSTRAINT PK_cost_v0ds PRIMARY KEY (stlc, v0ds)
);
CREATE INDEX IX_cost_v0ds_cur ON pricing.cost_v0ds(stlc, v0ds, curstdus);
CREATE OR ALTER PROCEDURE pricing.refresh_cost_rollups
AS
BEGIN
DELETE FROM pricing.cost_v1ds;
INSERT INTO
pricing.cost_v1ds
SELECT
trim(stlc) stlc
,trim(v1ds) v1ds
,avg(curstdus) curstdus
,avg(futstdus) futstdus
FROM
CMSInterfaceIN.[CMS.CUSLG].ITEMM i
LEFT OUTER JOIN pricing.arcstx a ON
a.part = i.item
AND a.plnt = i.dplt
LEFT OUTER JOIN pricing.plpr p ON
p.plnt = i.dplt
LEFT OUTER JOIN pricing.ffcret x ON
x.fcur = p.curr
AND x.tcur = 'US'
AND x.perd = p.ic
AND x.rtyp = 'ME'
WHERE
aplnt <> 'I'
AND stlc <> ''
AND substring(glec,1,1) <= '1'
GROUP BY
trim(stlc)
,trim(v1ds);
DELETE FROM pricing.cost_v0ds
INSERT INTO
pricing.cost_v0ds
SELECT
trim(stlc) stlc
,trim(colgrp)+trim(substring(branding,1,1)) v0ds
,avg(curstdus) curstdus
,avg(futstdus) futstdus
FROM
CMSInterfaceIN.[CMS.CUSLG].ITEMM i
LEFT OUTER JOIN pricing.arcstx a ON
a.part = i.item
AND a.plnt = i.dplt
LEFT OUTER JOIN pricing.plpr p ON
p.plnt = i.dplt
LEFT OUTER JOIN pricing.ffcret x ON
x.fcur = p.curr
AND x.tcur = 'US'
AND x.perd = p.ic
AND x.rtyp = 'ME'
WHERE
aplnt <> 'I'
AND stlc <> ''
AND substring(glec,1,1) <= '1'
GROUP BY
trim(stlc)
,trim(colgrp)+trim(substring(branding,1,1));
END

View File

@ -1,54 +0,0 @@
CREATE OR ALTER VIEW pricing.cust AS
SELECT
bvcust code,
bvbill default_billto,
bvname descr,
CASE WHEN bvadr6 = '' THEN bvname ELSE bvadr6 END dba,
bvctry country,
bvprcd province,
bvcity city,
bvcomp remit_to,
bvclas cclass,
bvstat status,
bvtype ctype,
RTRIM(bvschl) plevel,
s.bk7des3 folder,
-- pl.lists lists,
dr.repp default_rep,
rr.repp retail_rep,
gr.repp inside_rep,
nr.repp keyaccount_rep,
u.mfresp tier
FROM
cmsinterfacein.lgdat.cust c
LEFT OUTER JOIN cmsinterfacein.lgpgm.usrcust ON
cucust = bvcust
LEFT OUTER JOIN pricing.repc dr ON
dr.rcode = bvsalm
LEFT OUTER JOIN pricing.repc rr ON
rr.rcode = currep
LEFT OUTER JOIN pricing.repc gr ON
gr.rcode = cugrep
LEFT OUTER JOIN pricing.repc nr ON
nr.rcode = cunrep
LEFT OUTER JOIN pricing.sach s ON
s.bk7code = c.bvschl
LEFT OUTER JOIN pricing.ffterr t ON
t.prov = bvprcd
AND t.ctry = bvctry
LEFT OUTER JOIN pricing.ffcret x ON
x.fcur = c.bvcurr
AND x.tcur = 'US'
AND x.perd = (SELECT fspr FROM pricing.gld WHERE GETDATE() BETWEEN sdat AND edat)
AND x.rtyp = 'MA'
LEFT OUTER JOIN cmsinterfacein.lgdat.usrc u ON
u.mfsrce = 'MN'
AND u.mfent# = 12
AND u.mfkey2 = c.bvcust
-- LEFT OUTER JOIN (
-- SELECT jbplvl, STRING_AGG(JBPLCD, ', ') AS lists
-- FROM CMSinterfacein.[CMS.CUSLG].iprcbhc
-- GROUP BY jbplvl
-- ) AS pl ON
-- pl.jbplvl = c.bvschl
-- WHERE CASE WHEN bvadr6 = '' THEN bvname ELSE bvadr6 END = 'GRIFFIN'

View File

@ -1 +0,0 @@
SELECT * INTO pricing.ffcret FROM fanalysis.rlarp.ffcret

View File

@ -1 +0,0 @@
SELECT * INTO pricing.ffterr FROM fanalysis.rlarp.ffterr

View File

@ -1 +0,0 @@
SELECT * INTO pricing.gld FROM fanalysis.rlarp.gld

View File

@ -1,7 +1,237 @@
CREATE TABLE pricing.lastpricedetail (
customer varchar(255),
partgroup varchar(10),
part_stats nvarchar(MAX)
);
--------------------------------------------------------------------------------
-- Reset target tables
--------------------------------------------------------------------------------
--DROP TABLE IF EXISTS pricing.lastpricedetail;
DELETE FROM pricing.lastpricedetail;
DROP TABLE IF EXISTS #flagged;
CREATE UNIQUE NONCLUSTERED INDEX lastprice_cust_partgroup ON pricing.lastpricedetail ( customer ASC , partgroup ASC );
--------------------------------------------------------------------------------
-- Stage 1: Load cleaned input rows
-- Filters out irrelevant quotes/orders and calculates unit prices
--------------------------------------------------------------------------------
WITH base AS (
SELECT
o."Customer" AS customer,
o."Part Group" AS partgroup,
RTRIM(i.V1DS) AS dataseg,
o."Data Source" AS version,
o."Part Code" AS part,
o."Units" AS qty,
CASE
WHEN o."Units" = 0 THEN NULL
ELSE ROUND(o.[Value USD] / NULLIF(o."Units", 0), 5)
END AS price,
o.[Order Date] AS odate,
o.[Order Number] AS ordnum,
o.[Quote Number] AS quoten
FROM
rlarp.osm_stack_pretty o
INNER JOIN CMSInterfaceIn.[CMS.CUSLG].ITEMM i
ON i.item = o.[Part Code]
WHERE
o.[Data Source] IN ('Actual', 'Quotes')
AND o."Customer" IS NOT NULL
AND o."Financial Statement Line" = '41010'
AND o."Order Status" <> 'CANCELLED'
AND o."Units" > 0
AND o."Part Group" <> ''
-- Optional filter for testing
-- AND o."Customer" = 'ESBENSHADES GREENHOUSE'
),
--------------------------------------------------------------------------------
-- Stage 2: Rank each row based on recency and volume rules
-- Flags include:
-- - rn_mrs: most recent sale
-- - rn_mrq: most recent quote
-- - rn_lvs: largest sale in last year
-- - rn_lvq: largest quote in last year
-- - rn_dss: most recent sale per dataseg
-- - rn_dsq: most recent quote per dataseg
--------------------------------------------------------------------------------
ranked AS (
SELECT
b.*
-- Most recent sale
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY CASE WHEN b.version = 'Actual' THEN b.odate ELSE NULL END DESC
) AS rn_mrs
-- Most recent quote
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY CASE WHEN b.version = 'Quotes' THEN b.odate ELSE NULL END DESC
) AS rn_mrq
-- Largest volume sale (last 12 months)
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY CASE
WHEN b.version = 'Actual' AND b.odate >= DATEADD(YEAR, -1, GETDATE())
THEN b.qty ELSE NULL
END DESC
) AS rn_lvs
-- Largest volume quote (last 12 months)
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY CASE
WHEN b.version = 'Quotes' AND b.odate >= DATEADD(YEAR, -1, GETDATE())
THEN b.qty ELSE NULL
END DESC
) AS rn_lvq
-- Most recent sale per data segment
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY CASE WHEN b.version = 'Actual' THEN b.odate ELSE NULL END DESC
) AS rn_dss
-- Most recent quote per data segment
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY CASE WHEN b.version = 'Quotes' THEN b.odate ELSE NULL END DESC
) AS rn_dsq
FROM base b
)
--------------------------------------------------------------------------------
-- Stage 2.5: Save only rows that meet any of the above criteria
-- and annotate each with global-level flag (mrs, mrq, lvs, lvq)
--------------------------------------------------------------------------------
SELECT
*,
CASE WHEN rn_mrs = 1 THEN 'mrs' END AS f1,
CASE WHEN rn_mrq = 1 THEN 'mrq' END AS f2,
CASE WHEN rn_lvs = 1 THEN 'lvs' END AS f3,
CASE WHEN rn_lvq = 1 THEN 'lvq' END AS f4,
CASE WHEN rn_dss = 1 AND version = 'Actual' THEN 'dss' END AS f5,
CASE WHEN rn_dsq = 1 AND version = 'Quotes' THEN 'dsq' END AS f6
INTO #flagged
FROM ranked
WHERE
rn_mrs = 1
OR rn_mrq = 1
OR rn_lvs = 1
OR rn_lvq = 1
OR (rn_dss = 1 AND version = 'Actual')
OR (rn_dsq = 1 AND version = 'Quotes');
CREATE NONCLUSTERED INDEX ix_flagged_lookup
ON #flagged(customer, partgroup, dataseg, version, part, qty, price, odate, ordnum, quoten);
--------------------------------------------------------------------------------
-- Stage 3: Build JSON from flagged rows
--------------------------------------------------------------------------------
-- Step 3.1: Explode all flags from the #flagged table
WITH exploded_flags AS (
SELECT
customer, partgroup, part, dataseg, version, part, qty, price, odate, ordnum, quoten,
flag
FROM #flagged
CROSS APPLY (VALUES (f1), (f2), (f3), (f4), (f5), (f6)) AS f(flag)
WHERE flag IS NOT NULL
)
--SELECT * FROM exploded_flags
-- Step 3.2: Serialize each row into its JSON snippet
-- Carry odate and version for deduplication in seg_pieces
,serialized_flags AS (
SELECT
customer,
partgroup,
dataseg,
flag,
odate,
version,
CONCAT(
'"', flag, '":',
JSON_QUERY((
SELECT
version,
dataseg AS datasegment,
part,
qty,
price,
odate,
ordnum,
quoten,
flag
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
))
) AS json_piece
FROM exploded_flags
)
--SELECT * FROM serialized_flags
-- Step 3.3: Collect all global-level flags (mrs, mrq, lvs, lvq)
,flag_json AS (
SELECT
customer,
partgroup,
STRING_AGG(json_piece, ',') AS json_block
FROM serialized_flags
WHERE flag IN ('mrs', 'mrq', 'lvs', 'lvq')
GROUP BY customer, partgroup
)
--SELECT * FROM flag_json
-- Step 3.4: Nest dss/dsq under each dataseg
-- Only keep the most recent dss/dsq per dataseg/version (prevents duplicate keys)
,seg_pieces AS (
SELECT
customer,
partgroup,
dataseg,
STRING_AGG(json_piece, ',') AS inner_json
FROM (
SELECT sf.*
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, flag
ORDER BY odate DESC,
CASE WHEN version = 'Actual' THEN 1 ELSE 0 END DESC
) AS rn
FROM serialized_flags
WHERE flag IN ('dss', 'dsq')
) sf
WHERE sf.rn = 1
) deduped
GROUP BY customer, partgroup, dataseg
)
--SELECT * FROM seg_pieces
-- Step 3.5: Wrap the inner_json under dataseg key
,wrapped_segs AS (
SELECT
customer,
partgroup,
CONCAT(
'"', dataseg, '": {', inner_json, '}'
) AS json_piece
FROM seg_pieces
)
-- Step 3.6: Aggregate all dataseg entries into one JSON block per customer/partgroup
,seg_json AS (
SELECT
customer,
partgroup,
STRING_AGG(json_piece, ',') AS json_block
FROM wrapped_segs
GROUP BY customer, partgroup
)
--SELECT * FROM seg_json
--------------------------------------------------------------------------------
-- Stage 4: Merge flags and segment blocks into a single JSON object
-- Write final pricing history to pricing.lastpricedetail
--------------------------------------------------------------------------------
INSERT INTO
pricing.lastpricedetail
SELECT
COALESCE(f.customer, s.customer) AS customer,
COALESCE(f.partgroup, s.partgroup) AS partgroup,
CONCAT(
'{',
COALESCE(f.json_block, ''),
CASE
WHEN f.json_block IS NOT NULL AND s.json_block IS NOT NULL THEN ','
ELSE ''
END,
COALESCE(s.json_block, ''),
'}'
) AS part_stats
FROM flag_json f
FULL OUTER JOIN seg_json s
ON f.customer = s.customer AND f.partgroup = s.partgroup;

View File

@ -1,6 +1,6 @@
-- REFRESH MATERIALIZED VIEW pricequote.lastpricedetail;
REFRESH MATERIALIZED VIEW pricequote.lastpricedetail;
DROP MATERIALIZED VIEW pricequote.lastpricedetail;
--DROP MATERIALIZED VIEW pricequote.lastpricedetail
CREATE MATERIALIZED VIEW pricequote.lastpricedetail AS
WITH base AS (
@ -28,38 +28,38 @@ WITH base AS (
ranked AS (
SELECT b.*,
-- Most recent sale (Actuals first, newest date first)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER (
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup
ORDER BY (version = 'Actual') DESC,
odate DESC NULLS LAST
) END AS rn_mrs,
) AS rn_mrs,
-- Most recent quote (Quotes first, newest date first)
CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER (
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup
ORDER BY (version = 'Quotes') DESC,
odate DESC NULLS LAST
) END AS rn_mrq,
) AS rn_mrq,
-- Largest volume sale in last year (those inside window first)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER (
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup
ORDER BY (version = 'Actual' AND odate >= CURRENT_DATE - INTERVAL '1 year') DESC,
qty DESC NULLS LAST
) END AS rn_lvs,
) AS rn_lvs,
-- Largest volume quote in last year (those inside window first)
CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER (
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup
ORDER BY (version = 'Quotes' AND odate >= CURRENT_DATE - INTERVAL '1 year') DESC,
qty DESC NULLS LAST
) END AS rn_lvq,
) AS rn_lvq,
-- Per dataseg/version: most recent (version fixed in partition, so just date)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER (
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, version
ORDER BY odate DESC NULLS LAST
) END AS rn_dss,
CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER (
) AS rn_dss,
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, version
ORDER BY odate DESC NULLS LAST
) END AS rn_dsq
) AS rn_dsq
FROM base b
),
flagged AS (

View File

@ -1,20 +0,0 @@
CREATE OR ALTER VIEW pricing.plpr AS
SELECT
yaplnt plnt,
LTRIM(RTRIM(a9)) AS comp,
a30 AS descr,
SUBSTRING(a249, 242, 2) curr,
SUBSTRING(a249, 32, 4) AS gl,
SUBSTRING(a249, 190, 4) AS ar,
SUBSTRING(a249, 182, 4) AS ap,
SUBSTRING(a249, 198, 4) AS fa,
SUBSTRING(a249, 238, 4) AS ic
FROM
CMSInterfaceIN.lgdat.plnt
INNER JOIN CMSInterfaceIN.lgdat.code ON
yacomp = LTRIM(RTRIM(a9))
LEFT OUTER JOIN CMSInterfaceIN.lgdat.name ON
'C0000' + LTRIM(RTRIM(a9)) = a7
WHERE
a2 = 'AA'
OR a2 IS NULL;

View File

@ -1,12 +1,199 @@
DROP TABLE pricing.pricelist_ranged;
CREATE TABLE pricing.pricelist_ranged (
jcplcd varchar(5) ,
jcpart varchar(20) ,
jcunit varchar(3) ,
jcvoll numeric(12,5) ,
jcpric numeric(12,5) ,
vb_from float ,
vb_to float ,
price float
jcplcd varchar(5) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcpart varchar(20) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcunit varchar(3) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcvoll numeric(12,5) NOT NULL,
jcpric numeric(12,5) NOT NULL,
vb_from float NULL,
vb_to float NULL,
price float NOT NULL
);
CREATE NONCLUSTERED INDEX pricelist_ranged_idx ON pricing.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC );
CREATE NONCLUSTERED INDEX pricelist_ranged_idx ON FAnalysis.PRICING.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ) ;
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----------------------------------------------------------traverse unit of measure graph-----------------------------------------------------------------------
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-------------setup table to hold target conversions---------------------
SELECT DISTINCT
jcpart partn
,jcunit fu
,'PC' tu
,cast(null as numeric) factor
INTO
#anchor
FROM
cmsinterfacein.lgdat.iprcc
WHERE
1=1;
--SELECT * FROM #anchor
-------pre-build punit stacked on itself with the columns flipped so you can go either direction per join---------
SELECT
*
INTO
#g
FROM
(
SELECT
IHPART IHPART,
rtrim(IHUNT1) IHUNT1,
rtrim(IHUNT2) IHUNT2,
IHCNV1 IHCNV1,
IHCNV2 IHCNV2
FROM
CMSInterfaceIN.LGDAT.PUNIT pu
--only deal with parts in the anchor table or the &&global parts
INNER JOIN (
SELECT DISTINCT partn FROM #anchor
) items ON
items.partn = pu.ihpart
OR pu.ihpart = '&&GLOBAL'
UNION
SELECT
IHPART IHPART,
rtrim(IHUNT2) IHUNT1,
rtrim(IHUNT1) IHUNT2,
IHCNV2 IHCNV1,
IHCNV1 IHCNV2
FROM
CMSInterfaceIN.LGDAT.PUNIT pu
--only deal with parts in the anchor table or the &&global parts
INNER JOIN (
SELECT DISTINCT partn FROM #anchor
) items ON
items.partn = pu.ihpart
OR pu.ihpart = '&&GLOBAL'
) x ;
CREATE INDEX g_idx on #g(ihpart,ihunt1);
WITH
--------do the expansion on all paths until the target uom is matched----------------------------------------------
--(complains about types not matching between anchor and recursion, explicitly just casting everything)
uom (partn, partx, lvl, mastf, mastt, xf, xt, factor, xfactor, xnum, xden, id, uom_list) AS
(
SELECT
cast(partn as varchar(20)) --partn
,cast(partn as varchar(20)) --partx
,cast(0 as int) --lvl
,fu --mastf
,tu --mastt
,cast(fu as varchar(3)) --xf
,cast(fu as varchar(3)) --xt
,CAST(1 AS FLOAT) --factor
,CAST(1 AS FLOAT) --xfactor
,CAST(1 AS FLOAT) --xnum
,CAST(1 AS FLOAT) --xden
,format(row_number() over (ORDER BY partn),'000000')
,cast(trim(fu) as varchar(max))
FROM
#anchor
UNION ALL
SELECT
cast(uom.partn as varchar(20)) --partn
,cast(ihpart as varchar(20)) --partx
,CAST(uom.lvl + 1 AS INT) --lvl
,uom.mastf --mastf
,uom.mastt --mastt
,cast(p.ihunt1 as varchar(3)) --xf
,cast(p.ihunt2 as varchar(3)) --xt
,CAST(p.ihcnv2/p.ihcnv1 AS FLOAT) --factor
,CAST(p.ihcnv2/p.ihcnv1 AS FLOAT) * uom.xfactor --xfactor
,p.ihcnv2 * uom.xnum --xnum
,p.ihcnv1 * uom.xden --xden
,uom.id + '.' + format(row_number() over (PARTITION BY uom.id ORDER BY partn),'00')
,uom.uom_list + '.' + trim(p.ihunt2)
FROM
uom
INNER JOIN #g p ON
p.ihpart IN (uom.partn,'&&GLOBAL')
AND p.ihunt1 = uom.xt
WHERE
1=1
--AND p.ihunt2 not in ('BD','BG','BU','BX','CA','CS','PA','PL','SL','C','K','DOZ','PR')
AND p.ihunt1 <> uom.mastt
--prevent recursion: newest joined UOM can't be in the history
AND charindex(p.ihunt2,uom.uom_list) = 0
)
--SELECT COUNT(*) FROM UOM
--------------uom is going to have multiple rows per requested conversion, need to use row_number to pick the best row------------------------------
,sorted AS (
SELECT
partn, mastf from_uom, xt to_uom, xfactor factor, lvl steps, row_number() OVER (PARTITION BY partn, mastf, mastt ORDER BY lvl ASC, factor ASC) rn
FROM
uom
WHERE
xt = mastt
)
SELECT * INTO #uom FROM sorted WHERE rn = 1;
--so far so good
drop table #anchor;
drop table #g;
TRUNCATE TABLE pricing.pricelist_ranged;
WITH
conv AS (
SELECT
p.jcplcd,
p.jcpart,
p.jcunit,
p.jcvoll,
p.jcpric,
u.factor,
-- Normalize volume and price to PC
p.jcvoll * u.factor AS vol_pc,
p.jcpric / u.factor AS price_pc
FROM
cmsinterfacein.lgdat.iprcc p
INNER JOIN #uom u
ON u.partn = p.jcpart
AND u.from_uom = p.jcunit
AND u.to_uom = 'PC'
),
sorted AS (
SELECT
c.*,
ROW_NUMBER() OVER (PARTITION BY c.jcplcd, c.jcpart ORDER BY vol_pc ASC) AS rn
FROM conv c
),
ranged AS (
SELECT
curr.jcplcd,
curr.jcpart,
curr.jcunit,
curr.jcvoll,
curr.jcpric,
curr.vol_pc,
curr.price_pc,
curr.vol_pc AS vb_from,
COALESCE(next.vol_pc, 9999999.0) AS vb_to
FROM
sorted curr
LEFT JOIN sorted next
ON curr.jcplcd = next.jcplcd
AND curr.jcpart = next.jcpart
AND curr.rn + 1 = next.rn
)
INSERT INTO
pricing.pricelist_ranged
SELECT
RTRIM(jcplcd) jcplcd,
RTRIM(jcpart) jcpart,
jcunit,
jcvoll,
jcpric,
vb_from,
vb_to,
price_pc AS price
FROM
ranged;
--CREATE INDEX pricelist_ranged_idx ON pricing.pricelist_ranged(jcpart, jcplcd, vb_from, vb_to);

View File

@ -1,11 +1,101 @@
CREATE TABLE pricelist_ranged (
jcplcd text NULL,
jcpart text NULL,
jcunit text NULL,
jcvoll numeric(12, 5) NULL,
jcpric numeric(12, 5) NULL,
vb_from numeric NULL,
vb_to numeric NULL,
price numeric NULL
);
CREATE INDEX pricelist_ranged_idx ON pricequote.pricelist_ranged USING btree (jcpart, jcplcd, vb_from, vb_to);
DROP TABLE IF EXISTS uomc;
CREATE TEMP TABLE uomc AS (
WITH
uom AS (
SELECT
uom.p part
,uom.f fu
,uom.t tu
,uom.nm/uom.dm conv
FROM
(
SELECT
jsonb_agg(row_to_json(d)::jsonb) jdoc
FROM
(
select distinct
jcpart partn
, jcunit fu
, 'PC' tu
from
lgdat.iprcc
WHERE
jcpart <> ''
) d
) c
JOIN LATERAL rlarp.uom_array(c.jdoc) uom ON TRUE
)
SELECT * FROM uom
) WITH DATA;
CREATE INDEX uom_idx ON uomc (part, fu, tu);
-- Clear the output table
TRUNCATE TABLE pricequote.pricelist_ranged;
--DROP TABLE pricequote.pricelist_ranged;
-- Compute normalized volume/price and ranges
--CREATE TABLE pricequote.pricelist_ranged AS (
WITH
conv AS (
SELECT
p.jcplcd,
p.jcpart,
p.jcunit,
p.jcvoll,
p.jcpric,
u.conv,
(p.jcvoll * u.conv) AS vol_pc,
(p.jcpric / NULLIF(u.conv, 0)) AS price_pc
FROM
lgdat.iprcc p
INNER JOIN uomc u
ON u.part = p.jcpart
AND u.fu = p.jcunit
AND u.tu = 'PC'
),
--SELECT * FROM conv LIMIT 1000
sorted AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY jcplcd, jcpart ORDER BY vol_pc ASC) AS rn
FROM conv
),
ranged AS (
SELECT
curr.jcplcd,
curr.jcpart,
curr.jcunit,
curr.jcvoll,
curr.jcpric,
curr.vol_pc,
curr.price_pc price,
curr.vol_pc AS vb_from,
COALESCE(next.vol_pc, 9999999.0) AS vb_to
FROM
sorted curr
LEFT JOIN sorted next
ON curr.jcplcd = next.jcplcd
AND curr.jcpart = next.jcpart
AND curr.rn + 1 = next.rn
)
--SELECT * FROM ranged
INSERT INTO pricequote.pricelist_ranged (
jcplcd, jcpart, jcunit, jcvoll, jcpric, vb_from, vb_to, price
)
SELECT
jcplcd,
jcpart,
jcunit,
jcvoll,
jcpric,
vb_from,
vb_to,
price
FROM ranged;
CREATE INDEX pricelist_ranged_idx ON pricequote.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ) ;

View File

@ -1 +0,0 @@
SELECT * INTO pricing.pricing.qrh FROM fanalysis.RLARP.QRH q

View File

@ -1,18 +0,0 @@
CREATE OR ALTER VIEW pricing.repc AS
WITH
code AS (
SELECT
ltrim(rtrim(c.a9)) rcode
,(ltrim(rtrim(c.a9)) + ' - ') + c.a30 repp
FROM
CMSInterfaceIN.lgdat.code c
WHERE c.a2 = 'MM'
)
SELECT
COALESCE(c.rcode,q.qr) rcode
,COALESCE(c.repp,q.qr) repp
,COALESCE(q.dir,'Other') director
FROM
code c
FULL OUTER JOIN pricing.qrh q ON
q.qr = c.rcode

View File

@ -1 +0,0 @@
SELECT * INTO pricing.sach FROM fanalysis.lgdat.sach;

View File

@ -16,4 +16,26 @@ ALTER TABLE pricing.target_prices
ADD CONSTRAINT uq_target_prices_unique_combo
UNIQUE (stlc, ds, chan, tier, vol, lower_bound);
DELETE FROM pricing.target_prices;
INSERT INTO
pricing.target_prices
SELECT
stlc,
ds,
chan,
tier,
vol,
-- Extract lower bound: text between '[' and ','
TRY_CAST(SUBSTRING(vol, 2, CHARINDEX(',', vol) - 2) AS INT) AS lower_bound,
-- Extract upper bound: text between ',' and ')'
CASE
WHEN RIGHT(vol, 2) = ',)' THEN NULL
ELSE TRY_CAST(SUBSTRING(vol, CHARINDEX(',', vol) + 1, LEN(vol) - CHARINDEX(',', vol) - 1) AS INT)
END AS upper_bound,
price,
math
FROM
usmidsap02.ubm.pricequote.target_prices_view;
--SELECT COUNT(*) FROM pricing.target_prices

View File

@ -1,17 +0,0 @@
DROP TABLE pricequote.target_prices_base CASCADE;
CREATE TABLE pricequote.target_prices_base (
compset TEXT NOT NULL,
stlc TEXT NOT NULL,
floor NUMERIC NOT NULL,
ds TEXT NOT NULL,
chan TEXT NOT NULL,
tier TEXT NOT NULL,
vol INT4RANGE NOT NULL,
val NUMERIC NOT NULL,
price NUMERIC,
math TEXT[],
PRIMARY KEY (stlc, ds, chan, tier, vol)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON pricequote.target_prices_base TO PUBLIC;

View File

@ -8,4 +8,4 @@ SELECT
,price
,to_jsonb(math)::text AS math
FROM
pricequote.target_prices_base;
pricequote.target_prices;

View File

@ -1,77 +1,75 @@
{
"details": [
{
"label": "History",
"detailLevel": 10,
"label": "Model Inputs",
"details": [
{
"label": "Last Quote",
"detailLevel": 10,
"value": 0.1012,
"type": "currency",
"note": "XNS0T1G3G18B096 | Ord# 1008338 | 2025-06-12 | Qty 19,200"
"label": "Base Cost",
"value": 1.22446,
"type": "currency"
}
]
},
{
"label": "List",
"detailLevel": 10,
"label": "Peer Target",
"details": [
{
"label": "Code: GUAU",
"detailLevel": 10,
"value": 0.11,
"type": "currency",
"note": "List Min Qty: 9,600"
"label": "Peer Median Margin",
"value": 36.873,
"type": "percent",
"note": "DIR|102 - HANGING POTS|110 - INJECTION|Tier 2"
}
]
},
{
"label": "Target Calculation",
"detailLevel": 10,
"label": "Target Support",
"details": [
{
"label": "XNS0T1G3",
"detailLevel": 10,
"value": 0.08,
"label": "Tier 1 Truckload",
"value": 80,
"type": "currency",
"note": "Base Floor"
"note": "reviewed floor price"
},
{
"label": "Channel:WHS",
"detailLevel": 10,
"value": 0.2,
"type": "Percent",
"note": "Premium"
},
{
"label": "Volume:1-8",
"detailLevel": 10,
"value": 0.1,
"type": "Percent",
"note": "Premium"
},
{
"label": "Target",
"detailLevel": 10,
"value": 0.1056,
"type": "currency",
"note": "Total"
"label": "Warehouse",
"value": 1.2,
"type": "percent"
}
]
},
{
"label": "Guidance",
"detailLevel": 10,
"label": "Factor Adjustment",
"details": [
{
"label": "Price",
"detailLevel": 10,
"value": 0.1012,
"type": "currency",
"note": "Using target price, capped to not exceed last price"
"label": "Package UOM",
"value": -0.01,
"type": "percent"
},
{
"label": "# of Pallets",
"value": 0.02,
"type": "percent",
"note": "0.03"
},
{
"label": "Urgency",
"value": 0.03,
"type": "percent",
"note": "Top Priority"
},
{
"label": "State Adder/Subtractor",
"value": -0.02,
"type": "percent",
"note": "OH"
},
{
"label": "Branding",
"value": 0,
"type": "currency"
}
]
}
]
}