Trade Finance + Other changes

This commit is contained in:
AzureAD\SylvainDUVERNAY
2026-05-25 11:17:18 +02:00
parent 452e9e2632
commit 181de17b89
13 changed files with 1952 additions and 8 deletions

Binary file not shown.

View File

@@ -0,0 +1,267 @@
CREATE OR REPLACE VIEW public.vw_utility_physical_pricing_definition AS
SELECT
'Purchase'::text AS "strContractType",
pc.line AS "intContractLineId",
pc.id AS "intPricingComponentId",
pc.price_index AS "intPriceCurveId",
vpc."strPriceDescription",
vpc."strPriceIndex",
vpc."strCurrency",
vpc."strUnit",
vpc."strPriceCurveType",
vpc."strPriceArea",
vpc."strPriceCalendar",
vpc."strPricingType",
vpc."dtmPricingStartDate",
vpc."dtmPricingEndDate",
vpc."strPricingMonth"
FROM pricing_component pc
LEFT JOIN vw_utility_price_curve vpc
ON vpc."intPriceCurveId" = pc.price_index
WHERE pc.line IS NOT NULL
UNION ALL
SELECT
'Sale'::text AS "strContractType",
pc.sale_line AS "intContractLineId",
pc.id AS "intPricingComponentId",
pc.price_index AS "intPriceCurveId",
vpc."strPriceDescription",
vpc."strPriceIndex",
vpc."strCurrency",
vpc."strUnit",
vpc."strPriceCurveType",
vpc."strPriceArea",
vpc."strPriceCalendar",
vpc."strPricingType",
vpc."dtmPricingStartDate",
vpc."dtmPricingEndDate",
vpc."strPricingMonth"
FROM pricing_component pc
LEFT JOIN vw_utility_price_curve vpc
ON vpc."intPriceCurveId" = pc.price_index
WHERE pc.sale_line IS NOT NULL;
CREATE OR REPLACE VIEW public.vw_bi_itsa_physical_open_costs_v2 AS
WITH valuation_info AS (
SELECT
pvl."intContractId",
pvl."intContractLineId",
pvl."strState",
avg(pvl."dblPrice") AS "avgPrice",
avg(pvl."dblMtmPrice")::numeric(18, 2) AS "avgMtmPrice"
FROM vw_utility_physical_valuation_line pvl
GROUP BY
pvl."intContractId",
pvl."intContractLineId",
pvl."strState"
),
utility_open_quantity AS (
SELECT
CASE
WHEN uop."strContractSide" = 'purchase'::text THEN 'Purchase'::text
WHEN uop."strContractSide" = 'sale'::text THEN 'Sale'::text
ELSE NULL::text
END AS "TradeType",
uop."intContractLineId" AS "TradeLineId",
sum(uop."dblOpenQuantity")::numeric(18, 3) AS "OpenQuantitySigned"
FROM vw_utility_open_position uop
GROUP BY
CASE
WHEN uop."strContractSide" = 'purchase'::text THEN 'Purchase'::text
WHEN uop."strContractSide" = 'sale'::text THEN 'Sale'::text
ELSE NULL::text
END,
uop."intContractLineId"
),
contract_open_quantity AS (
SELECT
'Purchase'::text AS "TradeType",
pc."intPurchaseLineId" AS "TradeLineId",
abs(COALESCE(uop."OpenQuantitySigned", 0::numeric))::numeric(18, 3) AS "OpenQuantity",
COALESCE(uop."OpenQuantitySigned", 0::numeric)::numeric(18, 3) AS "OpenQuantitySigned",
COALESCE(vi."avgMtmPrice", 0::numeric(18, 2)) AS "AvgMtmPrice",
pc."dblTheoriticalQuantity" AS "TheoriticalQuantitySigned"
FROM vw_utility_purchase_physical_contract pc
LEFT JOIN utility_open_quantity uop
ON uop."TradeType" = 'Purchase'::text
AND uop."TradeLineId" = pc."intPurchaseLineId"
LEFT JOIN valuation_info vi
ON pc."intPurchaseLineId" = vi."intContractLineId"
UNION ALL
SELECT
'Sale'::text AS "TradeType",
pc."intSaleLineId" AS "TradeLineId",
abs(COALESCE(uop."OpenQuantitySigned", 0::numeric))::numeric(18, 3) AS "OpenQuantity",
COALESCE(uop."OpenQuantitySigned", 0::numeric)::numeric(18, 3) AS "OpenQuantitySigned",
COALESCE(vi."avgMtmPrice", 0::numeric(18, 2)) AS "AvgMtmPrice",
pc."dblTheoreticalQuantity" * '-1'::integer::numeric AS "TheoriticalQuantitySigned"
FROM vw_utility_sale_physical_contract pc
LEFT JOIN utility_open_quantity uop
ON uop."TradeType" = 'Sale'::text
AND uop."TradeLineId" = pc."intSaleLineId"
LEFT JOIN valuation_info vi
ON pc."intSaleLineId" = vi."intContractLineId"
)
SELECT
1 AS "Group Order",
'Price'::text AS "Group Type",
ctr."intPurchaseLineId" AS "intTradeLineId",
CASE
WHEN ctr."strPricingType"::text = 'basis'::text THEN COALESCE(prc."strPriceDescription", 'Basis Price Curve Missing'::character varying)
ELSE 'Fixed Price'::character varying
END AS "Line Description",
pur."dblUnitPrice" AS "Price",
opq."OpenQuantitySigned" AS "Open Quantity Signed",
opq."OpenQuantitySigned" * pur."dblUnitPrice" * '-1'::integer::numeric AS "Open Cost Amount",
1 AS "intPriceGroupId",
ctr."dtmEstimatedBLDate" AS "Estimated BL Date",
opq."TheoriticalQuantitySigned" AS "Contract Quantity Signed",
'Price'::text AS "Line Group"
FROM vw_utility_dim_physical_purchase_contract ctr
JOIN vw_utility_purchase_physical_contract pur
ON ctr."intPurchaseLineId" = pur."intPurchaseLineId"
JOIN contract_open_quantity opq
ON ctr."intPurchaseLineId" = opq."TradeLineId"
AND opq."TradeType" = 'Purchase'::text
LEFT JOIN vw_utility_physical_pricing_definition prc
ON prc."strContractType" = 'Purchase'::text
AND prc."intContractLineId" = ctr."intPurchaseLineId"
WHERE
ctr."strPricingType"::text IN ('priced'::text, 'basis'::text)
AND opq."OpenQuantity" <> 0::numeric
UNION ALL
SELECT
2 AS "Group Order",
'MTM'::text AS "Group Type",
ctr."intPurchaseLineId" AS "intTradeLineId",
mtm."strComponentCurve" AS "Line Description",
opq."AvgMtmPrice" AS "Price",
opq."OpenQuantitySigned" AS "Open Quantity Signed",
opq."OpenQuantitySigned" * opq."AvgMtmPrice" AS "Open Cost Amount",
3 AS "intPriceGroupId",
ctr."dtmEstimatedBLDate" AS "Estimated BL Date",
opq."TheoriticalQuantitySigned" AS "Contract Quantity Signed",
'MTM'::text AS "Line Group"
FROM vw_utility_physical_mtm_definition mtm
JOIN vw_utility_dim_physical_purchase_contract ctr
ON mtm."intContractLineId" = ctr."intPurchaseLineId"
JOIN contract_open_quantity opq
ON ctr."intPurchaseLineId" = opq."TradeLineId"
AND opq."TradeType" = 'Purchase'::text
WHERE
mtm."strContractType" = 'Purchase'::text
AND opq."OpenQuantity" <> 0::numeric
UNION ALL
SELECT
3 AS "Group Order",
'Fee'::text AS "Group Type",
fees."intContractLineId" AS "intTradeLineId",
fees."strProduct" AS "Line Description",
fees."dblPrice" AS "Price",
opq."OpenQuantitySigned" AS "Open Quantity Signed",
CASE
WHEN fees."strPayOrRec"::text = 'pay'::text THEN 1
ELSE '-1'::integer
END::numeric * opq."OpenQuantity" * fees."dblPrice" * '-1'::integer::numeric AS "Open Cost Amount",
2 AS "intPriceGroupId",
ctr."dtmEstimatedBLDate" AS "Estimated BL Date",
opq."TheoriticalQuantitySigned" AS "Contract Quantity Signed",
fees."strProductGroup" AS "Line Group"
FROM vw_utility_contract_fees fees
JOIN vw_utility_dim_physical_purchase_contract ctr
ON fees."intContractLineId" = ctr."intPurchaseLineId"
JOIN contract_open_quantity opq
ON ctr."intPurchaseLineId" = opq."TradeLineId"
AND opq."TradeType" = 'Purchase'::text
WHERE
fees."strContractType" = 'Purchase'::text
AND opq."OpenQuantity" <> 0::numeric
UNION ALL
SELECT
1 AS "Group Order",
'Price'::text AS "Group Type",
ctr."intSaleLineId" AS "intTradeLineId",
CASE
WHEN ctr."strPricingType"::text = 'basis'::text THEN COALESCE(prc."strPriceDescription", 'Basis Price Curve Missing'::character varying)
ELSE 'Fixed Price'::character varying
END AS "Line Description",
sale."dblUnitPrice" AS "Price",
opq."OpenQuantitySigned" AS "Open Quantity Signed",
opq."OpenQuantitySigned" * sale."dblUnitPrice" * '-1'::integer::numeric AS "Open Cost Amount",
1 AS "intPriceGroupId",
ctr."dtmEstimatedBLDate" AS "Estimated BL Date",
opq."TheoriticalQuantitySigned" AS "Contract Quantity Signed",
'Price'::text AS "Line Group"
FROM vw_utility_dim_physical_sale_contract ctr
JOIN vw_utility_sale_physical_contract sale
ON ctr."intSaleLineId" = sale."intSaleLineId"
JOIN contract_open_quantity opq
ON ctr."intSaleLineId" = opq."TradeLineId"
AND opq."TradeType" = 'Sale'::text
LEFT JOIN vw_utility_physical_pricing_definition prc
ON prc."strContractType" = 'Sale'::text
AND prc."intContractLineId" = ctr."intSaleLineId"
WHERE
ctr."strPricingType"::text IN ('priced'::text, 'basis'::text)
AND opq."OpenQuantity" <> 0::numeric
UNION ALL
SELECT
2 AS "Group Order",
'MTM'::text AS "Group Type",
ctr."intSaleLineId" AS "intTradeLineId",
mtm."strComponentCurve" AS "Line Description",
opq."AvgMtmPrice" AS "Price",
opq."OpenQuantitySigned" AS "Open Quantity Signed",
opq."OpenQuantitySigned" * opq."AvgMtmPrice" AS "Open Cost Amount",
3 AS "intPriceGroupId",
ctr."dtmEstimatedBLDate" AS "Estimated BL Date",
opq."TheoriticalQuantitySigned" AS "Contract Quantity Signed",
'MTM'::text AS "Line Group"
FROM vw_utility_physical_mtm_definition mtm
JOIN vw_utility_dim_physical_sale_contract ctr
ON mtm."intContractLineId" = ctr."intSaleLineId"
JOIN contract_open_quantity opq
ON ctr."intSaleLineId" = opq."TradeLineId"
AND opq."TradeType" = 'Sale'::text
WHERE
mtm."strContractType" = 'Sale'::text
AND opq."OpenQuantity" <> 0::numeric
UNION ALL
SELECT
3 AS "Group Order",
'Fee'::text AS "Group Type",
fees."intContractLineId" AS "intTradeLineId",
fees."strProduct" AS "Line Description",
fees."dblPrice" AS "Price",
opq."OpenQuantitySigned" AS "Open Quantity Signed",
CASE
WHEN fees."strPayOrRec"::text = 'pay'::text THEN 1
ELSE '-1'::integer
END::numeric * opq."OpenQuantity" * fees."dblPrice" * '-1'::integer::numeric AS "Open Cost Amount",
2 AS "intPriceGroupId",
ctr."dtmEstimatedBLDate" AS "Estimated BL Date",
opq."TheoriticalQuantitySigned" AS "Contract Quantity Signed",
fees."strProductGroup" AS "Line Group"
FROM vw_utility_contract_fees fees
JOIN vw_utility_dim_physical_sale_contract ctr
ON fees."intContractLineId" = ctr."intSaleLineId"
JOIN contract_open_quantity opq
ON ctr."intSaleLineId" = opq."TradeLineId"
AND opq."TradeType" = 'Sale'::text
WHERE
fees."strContractType" = 'Sale'::text
AND opq."OpenQuantity" <> 0::numeric;

View File

@@ -0,0 +1,154 @@
CREATE OR REPLACE VIEW public.vw_utility_open_position AS
WITH physical_lot_quantity AS (
SELECT
'purchase'::text AS side,
l.line AS contract_line_id,
SUM(COALESCE(qh.quantity, 0::numeric)) AS physical_quantity
FROM lot_lot l
LEFT JOIN lot_qt_hist qh
ON qh.lot = l.id
AND qh.quantity_type = l.lot_state
WHERE
l.lot_type::text = 'physic'::text
AND l.line IS NOT NULL
GROUP BY
l.line
UNION ALL
SELECT
'sale'::text AS side,
l.sale_line AS contract_line_id,
SUM(COALESCE(qh.quantity, 0::numeric)) AS physical_quantity
FROM lot_lot l
LEFT JOIN lot_qt_hist qh
ON qh.lot = l.id
AND qh.quantity_type = l.lot_state
WHERE
l.lot_type::text = 'physic'::text
AND l.sale_line IS NOT NULL
GROUP BY
l.sale_line
),
matched_virtual_quantity AS (
SELECT
'purchase'::text AS side,
lp.line AS contract_line_id,
SUM(lqt.lot_quantity) AS matched_quantity
FROM lot_qt lqt
JOIN lot_lot lp
ON lp.id = lqt.lot_p
WHERE
lp.lot_type::text = 'virtual'::text
AND lp.line IS NOT NULL
AND lqt.lot_s IS NOT NULL
AND lqt.lot_quantity > 0::numeric
GROUP BY
lp.line
UNION ALL
SELECT
'sale'::text AS side,
ls.sale_line AS contract_line_id,
SUM(lqt.lot_quantity) AS matched_quantity
FROM lot_qt lqt
JOIN lot_lot ls
ON ls.id = lqt.lot_s
WHERE
ls.lot_type::text = 'virtual'::text
AND ls.sale_line IS NOT NULL
AND lqt.lot_p IS NOT NULL
AND lqt.lot_quantity > 0::numeric
GROUP BY
ls.sale_line
),
open_virtual_lots AS (
SELECT
'purchase'::text AS side,
lqt.id AS int_lot_qt_id,
lp.id AS int_lot_id,
pl.id AS int_contract_line_id,
pp.party AS int_counterparty_id,
pl.product AS int_product_id,
uom.name AS uom,
lqt.lot_quantity AS open_quantity,
pl.from_del AS period_start,
pl.to_del AS period_end,
COALESCE(plq.physical_quantity, 0::numeric) AS physical_lot_quantity,
pl.quantity_theorical - COALESCE(plq.physical_quantity, 0::numeric)
- COALESCE(mvq.matched_quantity, 0::numeric)
AS open_targeted_quantity
FROM lot_qt lqt
JOIN lot_lot lp
ON lp.id = lqt.lot_p
JOIN purchase_line pl
ON pl.id = lp.line
JOIN purchase_purchase pp
ON pp.id = pl.purchase
LEFT JOIN product_uom uom
ON uom.id = lqt.lot_unit
LEFT JOIN physical_lot_quantity plq
ON plq.side = 'purchase'::text
AND plq.contract_line_id = pl.id
LEFT JOIN matched_virtual_quantity mvq
ON mvq.side = 'purchase'::text
AND mvq.contract_line_id = pl.id
WHERE
lp.lot_type::text = 'virtual'::text
AND lqt.lot_s IS NULL
AND COALESCE(pl.finished, false) = false
AND lqt.lot_quantity > 0::numeric
UNION ALL
SELECT
'sale'::text AS side,
lqt.id AS int_lot_qt_id,
ls.id AS int_lot_id,
sl.id AS int_contract_line_id,
ss.party AS int_counterparty_id,
sl.product AS int_product_id,
uom.name AS uom,
-lqt.lot_quantity AS open_quantity,
sl.from_del AS period_start,
sl.to_del AS period_end,
COALESCE(plq.physical_quantity, 0::numeric) AS physical_lot_quantity,
sl.quantity_theorical - COALESCE(plq.physical_quantity, 0::numeric)
- COALESCE(mvq.matched_quantity, 0::numeric)
AS open_targeted_quantity
FROM lot_qt lqt
JOIN lot_lot ls
ON ls.id = lqt.lot_s
JOIN sale_line sl
ON sl.id = ls.sale_line
JOIN sale_sale ss
ON ss.id = sl.sale
LEFT JOIN product_uom uom
ON uom.id = lqt.lot_unit
LEFT JOIN physical_lot_quantity plq
ON plq.side = 'sale'::text
AND plq.contract_line_id = sl.id
LEFT JOIN matched_virtual_quantity mvq
ON mvq.side = 'sale'::text
AND mvq.contract_line_id = sl.id
WHERE
ls.lot_type::text = 'virtual'::text
AND lqt.lot_p IS NULL
AND COALESCE(sl.finished, false) = false
AND lqt.lot_quantity > 0::numeric
)
SELECT
side AS "strContractSide",
int_lot_qt_id AS "intLotQtId",
int_lot_id AS "intLotId",
int_contract_line_id AS "intContractLineId",
int_counterparty_id AS "intCounterpartyId",
int_product_id AS "intProductId",
uom AS "strUom",
open_quantity AS "dblOpenQuantity",
period_start AS "dtmPeriodStart",
period_end AS "dtmPeriodEnd",
physical_lot_quantity AS "dblPhysicalLotQuantity",
open_targeted_quantity AS "dblOpenTargetedQuantity"
FROM open_virtual_lots;

View File

@@ -0,0 +1,495 @@
CREATE OR REPLACE VIEW public.vw_utility_shipment_fee_allocation AS
WITH physical_shipment_lots AS (
SELECT
l.lot_shipment_in AS shipment_id,
l.id AS lot_id,
l.lot_type,
l.line AS purchase_line_id,
l.sale_line AS sale_line_id,
qh.quantity AS shipment_quantity,
l.lot_unit_line AS shipment_unit_id
FROM lot_lot l
LEFT JOIN lot_qt_hist qh
ON qh.lot = l.id
AND qh.quantity_type = l.lot_state
WHERE
l.lot_type::text = 'physic'::text
AND l.lot_shipment_in IS NOT NULL
),
virtual_shipment_lots AS (
SELECT
q.lot_shipment_in AS shipment_id,
vp.id AS lot_id,
vp.lot_type,
vp.line AS purchase_line_id,
COALESCE(vp.sale_line, ps.sale_line) AS sale_line_id,
q.lot_quantity AS shipment_quantity,
q.lot_unit AS shipment_unit_id
FROM lot_qt q
JOIN lot_lot vp
ON vp.id = q.lot_p
AND vp.lot_type::text = 'virtual'::text
LEFT JOIN lot_lot ps
ON ps.id = q.lot_s
WHERE
q.lot_shipment_in IS NOT NULL
AND NOT EXISTS (
SELECT 1
FROM physical_shipment_lots pl
WHERE
pl.shipment_id = q.lot_shipment_in
AND pl.purchase_line_id = vp.line
AND (
pl.sale_line_id = COALESCE(vp.sale_line, ps.sale_line)
OR COALESCE(vp.sale_line, ps.sale_line) IS NULL
)
)
),
shipment_lots AS (
SELECT
physical_shipment_lots.shipment_id,
physical_shipment_lots.lot_id,
physical_shipment_lots.lot_type,
physical_shipment_lots.purchase_line_id,
physical_shipment_lots.sale_line_id,
physical_shipment_lots.shipment_quantity,
physical_shipment_lots.shipment_unit_id
FROM physical_shipment_lots
UNION ALL
SELECT
virtual_shipment_lots.shipment_id,
virtual_shipment_lots.lot_id,
virtual_shipment_lots.lot_type,
virtual_shipment_lots.purchase_line_id,
virtual_shipment_lots.sale_line_id,
virtual_shipment_lots.shipment_quantity,
virtual_shipment_lots.shipment_unit_id
FROM virtual_shipment_lots
),
shipment_context AS (
SELECT
shipment_lots.shipment_id,
shipment_lots.lot_id,
shipment_lots.purchase_line_id,
shipment_lots.sale_line_id,
shipment_lots.shipment_unit_id,
sum(COALESCE(shipment_lots.shipment_quantity, 0::numeric)) AS shipment_quantity
FROM shipment_lots
WHERE shipment_lots.shipment_id IS NOT NULL
GROUP BY
shipment_lots.shipment_id,
shipment_lots.lot_id,
shipment_lots.purchase_line_id,
shipment_lots.sale_line_id,
shipment_lots.shipment_unit_id
),
shipment_base AS (
SELECT
shipment_context.shipment_id,
sum(shipment_context.shipment_quantity) AS shipment_quantity
FROM shipment_context
GROUP BY shipment_context.shipment_id
),
shipment_fee_candidates AS (
SELECT
f.id AS fee_id,
'Shipment'::text AS fee_source,
f.type AS fee_type,
CASE
WHEN f.type::text = 'ordered'::text THEN 1
WHEN f.type::text = 'scheduled'::text THEN 2
ELSE NULL::integer
END AS priority,
sb.shipment_id,
f.product AS product_id,
f.supplier AS supplier_id,
f.mode AS packaging,
f.p_r AS pay_or_rec,
f.state,
f.weight_type,
COALESCE(f.quantity, 0::numeric) AS requested_quantity,
sb.shipment_quantity,
f.price AS fee_price,
f.currency AS currency_id,
f.unit AS unit_id,
CASE
WHEN upper(f.p_r::text) = 'REC'::text THEN 1
ELSE '-1'::integer
END AS sign_multiplier,
NULL::text AS forced_cost_group
FROM shipment_base sb
JOIN fee_fee f
ON f.shipment_in = sb.shipment_id
WHERE f.type::text = ANY (ARRAY['ordered'::character varying::text, 'scheduled'::character varying::text])
),
shipment_fee_limits AS (
SELECT
c.*,
LEAST(
GREATEST(c.requested_quantity, 0::numeric),
GREATEST(
c.shipment_quantity
- COALESCE(
sum(c.requested_quantity) OVER (
PARTITION BY c.shipment_id, c.product_id, c.supplier_id
ORDER BY c.priority, c.fee_id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0::numeric
),
0::numeric
)
) AS fee_allocatable_quantity
FROM shipment_fee_candidates c
),
shipment_fee_allocated AS (
SELECT
fl.fee_id,
fl.fee_source,
fl.fee_type,
fl.priority,
fl.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
fl.product_id,
fl.supplier_id,
fl.packaging,
fl.pay_or_rec,
fl.state,
fl.weight_type,
fl.requested_quantity,
fl.shipment_quantity,
fl.fee_price,
fl.currency_id,
COALESCE(fl.unit_id, sc.shipment_unit_id) AS unit_id,
fl.sign_multiplier,
fl.forced_cost_group,
LEAST(
sc.shipment_quantity,
GREATEST(
fl.fee_allocatable_quantity
- COALESCE(
sum(sc.shipment_quantity) OVER (
PARTITION BY fl.fee_id
ORDER BY sc.lot_id, sc.purchase_line_id, sc.sale_line_id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0::numeric
),
0::numeric
)
) AS allocated_quantity
FROM shipment_fee_limits fl
JOIN shipment_context sc
ON sc.shipment_id = fl.shipment_id
),
shipment_fee_coverage AS (
SELECT
shipment_fee_allocated.shipment_id,
shipment_fee_allocated.product_id,
shipment_fee_allocated.supplier_id,
max(shipment_fee_allocated.shipment_quantity) AS shipment_quantity,
sum(shipment_fee_allocated.allocated_quantity) AS allocated_quantity
FROM shipment_fee_allocated
GROUP BY
shipment_fee_allocated.shipment_id,
shipment_fee_allocated.product_id,
shipment_fee_allocated.supplier_id
),
contract_budgeted_fee_candidates AS (
SELECT
f.id AS fee_id,
'Purchase Contract'::text AS fee_source,
f.type AS fee_type,
3 AS priority,
sc.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
f.product AS product_id,
f.supplier AS supplier_id,
f.mode AS packaging,
f.p_r AS pay_or_rec,
f.state,
f.weight_type,
sc.shipment_quantity,
f.price AS fee_price,
f.currency AS currency_id,
COALESCE(f.unit, sc.shipment_unit_id) AS unit_id,
CASE
WHEN upper(f.p_r::text) = 'REC'::text THEN '-1'::integer
ELSE 1
END AS sign_multiplier,
NULL::text AS forced_cost_group
FROM shipment_context sc
JOIN fee_fee f
ON f.line = sc.purchase_line_id
WHERE
f.type::text = 'budgeted'::text
AND sc.purchase_line_id IS NOT NULL
UNION ALL
SELECT
f.id AS fee_id,
'Sale Contract'::text AS fee_source,
f.type AS fee_type,
3 AS priority,
sc.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
f.product AS product_id,
f.supplier AS supplier_id,
f.mode AS packaging,
f.p_r AS pay_or_rec,
f.state,
f.weight_type,
sc.shipment_quantity,
f.price AS fee_price,
f.currency AS currency_id,
COALESCE(f.unit, sc.shipment_unit_id) AS unit_id,
CASE
WHEN upper(f.p_r::text) = 'REC'::text THEN 1
ELSE '-1'::integer
END AS sign_multiplier,
NULL::text AS forced_cost_group
FROM shipment_context sc
JOIN fee_fee f
ON f.sale_line = sc.sale_line_id
WHERE
f.type::text = 'budgeted'::text
AND sc.sale_line_id IS NOT NULL
),
contract_budgeted_allocated AS (
SELECT
c.fee_id,
c.fee_source,
c.fee_type,
c.priority,
c.shipment_id,
c.lot_id,
c.purchase_line_id,
c.sale_line_id,
c.product_id,
c.supplier_id,
c.packaging,
c.pay_or_rec,
c.state,
c.weight_type,
c.shipment_quantity,
c.fee_price,
c.currency_id,
c.unit_id,
c.sign_multiplier,
c.forced_cost_group,
LEAST(
c.shipment_quantity,
GREATEST(
sum(c.shipment_quantity) OVER (
PARTITION BY c.shipment_id, c.product_id, c.supplier_id, c.fee_id
)
- COALESCE(fc.allocated_quantity, 0::numeric)
- COALESCE(
sum(c.shipment_quantity) OVER (
PARTITION BY c.shipment_id, c.product_id, c.supplier_id, c.fee_id
ORDER BY c.lot_id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0::numeric
),
0::numeric
)
) AS allocated_quantity
FROM contract_budgeted_fee_candidates c
LEFT JOIN shipment_fee_coverage fc
ON fc.shipment_id = c.shipment_id
AND fc.product_id = c.product_id
AND fc.supplier_id = c.supplier_id
),
price_rows AS (
SELECT
NULL::integer AS fee_id,
'Purchase Price'::text AS fee_source,
'price'::text AS fee_type,
4 AS priority,
sc.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
pl.product AS product_id,
NULL::integer AS supplier_id,
NULL::character varying AS packaging,
NULL::character varying AS pay_or_rec,
NULL::character varying AS state,
NULL::character varying AS weight_type,
sc.shipment_quantity AS allocated_quantity,
pl.unit_price AS fee_price,
pl.currency_ AS currency_id,
COALESCE(pl.unit, sc.shipment_unit_id) AS unit_id,
1 AS sign_multiplier,
'PurchasePrice'::text AS forced_cost_group
FROM shipment_context sc
JOIN purchase_line pl
ON pl.id = sc.purchase_line_id
WHERE sc.purchase_line_id IS NOT NULL
UNION ALL
SELECT
NULL::integer AS fee_id,
'Sale Price'::text AS fee_source,
'price'::text AS fee_type,
5 AS priority,
sc.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
sl.product AS product_id,
NULL::integer AS supplier_id,
NULL::character varying AS packaging,
NULL::character varying AS pay_or_rec,
NULL::character varying AS state,
NULL::character varying AS weight_type,
sc.shipment_quantity AS allocated_quantity,
sl.unit_price AS fee_price,
ss.currency AS currency_id,
COALESCE(sl.unit, sc.shipment_unit_id) AS unit_id,
'-1'::integer AS sign_multiplier,
'SalePrice'::text AS forced_cost_group
FROM shipment_context sc
JOIN sale_line sl
ON sl.id = sc.sale_line_id
JOIN sale_sale ss
ON ss.id = sl.sale
WHERE sc.sale_line_id IS NOT NULL
),
final_fees AS (
SELECT
shipment_fee_allocated.fee_id,
shipment_fee_allocated.fee_source,
shipment_fee_allocated.fee_type,
shipment_fee_allocated.priority,
shipment_fee_allocated.shipment_id,
shipment_fee_allocated.lot_id,
shipment_fee_allocated.purchase_line_id,
shipment_fee_allocated.sale_line_id,
shipment_fee_allocated.product_id,
shipment_fee_allocated.supplier_id,
shipment_fee_allocated.packaging,
shipment_fee_allocated.pay_or_rec,
shipment_fee_allocated.state,
shipment_fee_allocated.weight_type,
shipment_fee_allocated.allocated_quantity AS fee_quantity,
shipment_fee_allocated.fee_price,
shipment_fee_allocated.currency_id,
shipment_fee_allocated.unit_id,
shipment_fee_allocated.sign_multiplier,
shipment_fee_allocated.forced_cost_group
FROM shipment_fee_allocated
WHERE shipment_fee_allocated.allocated_quantity > 0::numeric
UNION ALL
SELECT
contract_budgeted_allocated.fee_id,
contract_budgeted_allocated.fee_source,
contract_budgeted_allocated.fee_type,
contract_budgeted_allocated.priority,
contract_budgeted_allocated.shipment_id,
contract_budgeted_allocated.lot_id,
contract_budgeted_allocated.purchase_line_id,
contract_budgeted_allocated.sale_line_id,
contract_budgeted_allocated.product_id,
contract_budgeted_allocated.supplier_id,
contract_budgeted_allocated.packaging,
contract_budgeted_allocated.pay_or_rec,
contract_budgeted_allocated.state,
contract_budgeted_allocated.weight_type,
contract_budgeted_allocated.allocated_quantity AS fee_quantity,
contract_budgeted_allocated.fee_price,
contract_budgeted_allocated.currency_id,
contract_budgeted_allocated.unit_id,
contract_budgeted_allocated.sign_multiplier,
contract_budgeted_allocated.forced_cost_group
FROM contract_budgeted_allocated
WHERE contract_budgeted_allocated.allocated_quantity > 0::numeric
UNION ALL
SELECT
price_rows.fee_id,
price_rows.fee_source,
price_rows.fee_type,
price_rows.priority,
price_rows.shipment_id,
price_rows.lot_id,
price_rows.purchase_line_id,
price_rows.sale_line_id,
price_rows.product_id,
price_rows.supplier_id,
price_rows.packaging,
price_rows.pay_or_rec,
price_rows.state,
price_rows.weight_type,
price_rows.allocated_quantity AS fee_quantity,
price_rows.fee_price,
price_rows.currency_id,
price_rows.unit_id,
price_rows.sign_multiplier,
price_rows.forced_cost_group
FROM price_rows
WHERE price_rows.allocated_quantity > 0::numeric
)
SELECT
f.shipment_id AS "intShipmentId",
f.lot_id AS "intLotId",
COALESCE(f.fee_id, 0) AS "intFeeId",
f.fee_source AS "strFeeSource",
f.fee_type AS "strFeeType",
f.priority AS "intPriority",
COALESCE(f.purchase_line_id, 0) AS "intPurchaseLineId",
COALESCE(f.sale_line_id, 0) AS "intSaleLineId",
COALESCE(f.product_id, 0) AS "intProductId",
COALESCE(p.code, ''::character varying) AS "strFee",
COALESCE(f.supplier_id, 0) AS "intSupplierId",
COALESCE(sup.name, ''::character varying) AS "strSupplier",
COALESCE(f.packaging, ''::character varying) AS "strPackaging",
COALESCE(f.pay_or_rec, ''::character varying) AS "strPayOrRec",
COALESCE(f.state, ''::character varying) AS "strState",
CASE
WHEN upper(f.weight_type::text) = 'BRUT'::text THEN 'Gross'::text
ELSE 'Net'::text
END AS "strWeighingType",
f.fee_quantity AS "dblQuantity",
f.fee_price AS "dblPrice",
COALESCE(cur.name, ''::character varying) AS "strCurrency",
COALESCE(uom.name, 'Mt'::character varying) AS "strUnit",
f.fee_quantity * f.fee_price AS "dblAmount",
COALESCE(
f.forced_cost_group,
CASE
WHEN upper(p.code::text) ~~ '%FREIGHT%'::text THEN 'Freight'::text
WHEN upper(p.code::text) ~~ '%PROFIT SHARING%'::text THEN 'Profit Sharing'::text
ELSE 'Other Costs'::text
END
) AS "strCostGroup",
CASE
WHEN f.pay_or_rec::text = 'pay'::text THEN - 1::numeric
WHEN f.pay_or_rec::text = 'rec'::text THEN 1::numeric
WHEN f.fee_source = 'Purchase Price'::text AND f.fee_type::text = 'price'::text THEN - 1::numeric
WHEN f.fee_source = 'Sale Price'::text AND f.fee_type::text = 'price'::text THEN 1::numeric
ELSE - 1::numeric
END AS "intSignMultiplier"
FROM final_fees f
JOIN product_product p
ON p.id = f.product_id
LEFT JOIN party_party sup
ON sup.id = f.supplier_id
LEFT JOIN currency_currency cur
ON cur.id = f.currency_id
LEFT JOIN product_uom uom
ON uom.id = f.unit_id;

View File

@@ -0,0 +1,382 @@
CREATE OR REPLACE VIEW public.vw_utility_trade_pnl_quantities AS
WITH lot_current_qty AS (
SELECT
l.id AS lot_id,
round(
COALESCE(lh.quantity, 0::numeric)
* COALESCE(lot_uom.factor, 1::double precision)::numeric
/ NULLIF(COALESCE(line_uom.factor, 1::double precision)::numeric, 0::numeric),
5
) AS pnl_quantity,
lot_uom.name AS quantity_uom
FROM lot_lot l
LEFT JOIN lot_qt_hist lh ON lh.lot = l.id AND lh.quantity_type = l.lot_state
LEFT JOIN product_uom lot_uom ON lot_uom.id = l.lot_unit_line
LEFT JOIN purchase_line pl ON pl.id = l.line
LEFT JOIN sale_line sl ON sl.id = l.sale_line
LEFT JOIN product_uom line_uom ON line_uom.id = COALESCE(pl.unit, sl.unit)
), purchase_pnl_lots AS (
SELECT
'purchase'::text AS pnl_side,
p.id AS purchase_id,
p.number AS purchase_number,
p.reference AS purchase_reference,
pl.id AS purchase_line_id,
pl.finished AS purchase_line_finished,
s.id AS sale_id,
s.number AS sale_number,
s.reference AS sale_reference,
sl.id AS sale_line_id,
sl.finished AS sale_line_finished,
l.id AS valued_lot_id,
l.lot_type AS valued_lot_type,
NULL::integer AS lot_qt_id,
lcq.pnl_quantity,
lcq.quantity_uom AS quantity_unit,
pl.product AS product_id,
COALESCE(shi.from_location, sint.from_location, sho.from_location, p.from_location) AS from_location_id,
COALESCE(shi.to_location, sint.to_location, sho.to_location, p.to_location) AS to_location_id,
shi.id AS shipment_id,
shi.bl_date,
pl.from_del AS delivery_date_from,
pl.to_del AS delivery_date_to
FROM purchase_line pl
JOIN purchase_purchase p ON p.id = pl.purchase
JOIN lot_lot l ON l.line = pl.id
JOIN lot_current_qty lcq ON lcq.lot_id = l.id
LEFT JOIN sale_line sl ON sl.id = l.sale_line
LEFT JOIN sale_sale s ON s.id = sl.sale
LEFT JOIN stock_shipment_in shi ON shi.id = l.lot_shipment_in
LEFT JOIN stock_shipment_internal sint ON sint.id = l.lot_shipment_internal
LEFT JOIN stock_shipment_out sho ON sho.id = l.lot_shipment_out
WHERE pl.type::text = 'line'::text
AND (l.lot_type::text = ANY (ARRAY['virtual'::character varying::text, 'physic'::character varying::text]))
), sale_pnl_direct_lots AS (
SELECT
'sale'::text AS pnl_side,
p.id AS purchase_id,
p.number AS purchase_number,
p.reference AS purchase_reference,
pl.id AS purchase_line_id,
pl.finished AS purchase_line_finished,
s.id AS sale_id,
s.number AS sale_number,
s.reference AS sale_reference,
sl.id AS sale_line_id,
sl.finished AS sale_line_finished,
l.id AS valued_lot_id,
l.lot_type AS valued_lot_type,
NULL::integer AS lot_qt_id,
lcq.pnl_quantity,
lcq.quantity_uom AS quantity_unit,
sl.product AS product_id,
COALESCE(shi.from_location, sint.from_location, sho.from_location, s.from_location) AS from_location_id,
COALESCE(shi.to_location, sint.to_location, sho.to_location, s.to_location) AS to_location_id,
shi.id AS shipment_id,
shi.bl_date,
sl.from_del AS delivery_date_from,
sl.to_del AS delivery_date_to
FROM purchase_line pl
JOIN purchase_purchase p ON p.id = pl.purchase
JOIN lot_lot l ON l.line = pl.id AND l.sale_line IS NOT NULL
JOIN sale_line sl ON sl.id = l.sale_line
JOIN sale_sale s ON s.id = sl.sale
JOIN lot_current_qty lcq ON lcq.lot_id = l.id
LEFT JOIN stock_shipment_in shi ON shi.id = l.lot_shipment_in
LEFT JOIN stock_shipment_internal sint ON sint.id = l.lot_shipment_internal
LEFT JOIN stock_shipment_out sho ON sho.id = l.lot_shipment_out
WHERE pl.type::text = 'line'::text
AND l.lot_type::text = 'physic'::text
AND NOT EXISTS (
SELECT 1
FROM purchase_pnl_lots ppl
WHERE ppl.valued_lot_id = l.id
AND ppl.sale_line_id = sl.id
)
), sale_pnl_matched_virtual_lots AS (
SELECT
'sale'::text AS pnl_side,
p.id AS purchase_id,
p.number AS purchase_number,
p.reference AS purchase_reference,
pl.id AS purchase_line_id,
pl.finished AS purchase_line_finished,
s.id AS sale_id,
s.number AS sale_number,
s.reference AS sale_reference,
sl.id AS sale_line_id,
sl.finished AS sale_line_finished,
sale_lot.id AS valued_lot_id,
sale_lot.lot_type AS valued_lot_type,
lqt.id AS lot_qt_id,
lcq.pnl_quantity,
lcq.quantity_uom AS quantity_unit,
sl.product AS product_id,
COALESCE(shi.from_location, sint.from_location, sho.from_location, s.from_location) AS from_location_id,
COALESCE(shi.to_location, sint.to_location, sho.to_location, s.to_location) AS to_location_id,
shi.id AS shipment_id,
shi.bl_date,
sl.from_del AS delivery_date_from,
sl.to_del AS delivery_date_to
FROM purchase_line pl
JOIN purchase_purchase p ON p.id = pl.purchase
JOIN lot_lot purchase_lot ON purchase_lot.line = pl.id
AND purchase_lot.lot_type::text = 'virtual'::text
AND purchase_lot.sale_line IS NULL
JOIN lot_qt lqt ON lqt.lot_p = purchase_lot.id
AND lqt.lot_s IS NOT NULL
AND lqt.lot_quantity > 0::numeric
JOIN lot_lot sale_lot ON sale_lot.id = lqt.lot_s
AND sale_lot.lot_type::text = 'virtual'::text
JOIN sale_line sl ON sl.id = sale_lot.sale_line
JOIN sale_sale s ON s.id = sl.sale
JOIN lot_current_qty lcq ON lcq.lot_id = sale_lot.id
LEFT JOIN stock_shipment_in shi ON shi.id = lqt.lot_shipment_in
LEFT JOIN stock_shipment_internal sint ON sint.id = lqt.lot_shipment_internal
LEFT JOIN stock_shipment_out sho ON sho.id = lqt.lot_shipment_out
WHERE pl.type::text = 'line'::text
), sale_pnl_unmatched_lots AS (
SELECT
'sale'::text AS pnl_side,
NULL::integer AS purchase_id,
NULL::character varying AS purchase_number,
NULL::character varying AS purchase_reference,
NULL::integer AS purchase_line_id,
NULL::boolean AS purchase_line_finished,
s.id AS sale_id,
s.number AS sale_number,
s.reference AS sale_reference,
sl.id AS sale_line_id,
sl.finished AS sale_line_finished,
l.id AS valued_lot_id,
l.lot_type AS valued_lot_type,
NULL::integer AS lot_qt_id,
lcq.pnl_quantity,
lcq.quantity_uom AS quantity_unit,
sl.product AS product_id,
COALESCE(shi.from_location, sint.from_location, sho.from_location, s.from_location) AS from_location_id,
COALESCE(shi.to_location, sint.to_location, sho.to_location, s.to_location) AS to_location_id,
shi.id AS shipment_id,
shi.bl_date,
sl.from_del AS delivery_date_from,
sl.to_del AS delivery_date_to
FROM sale_line sl
JOIN sale_sale s ON s.id = sl.sale
JOIN lot_lot l ON l.sale_line = sl.id
AND l.line IS NULL
AND (l.lot_type::text = ANY (ARRAY['virtual'::character varying::text, 'physic'::character varying::text]))
JOIN lot_current_qty lcq ON lcq.lot_id = l.id
LEFT JOIN stock_shipment_in shi ON shi.id = l.lot_shipment_in
LEFT JOIN stock_shipment_internal sint ON sint.id = l.lot_shipment_internal
LEFT JOIN stock_shipment_out sho ON sho.id = l.lot_shipment_out
WHERE sl.type::text = 'line'::text
AND NOT EXISTS (
SELECT 1
FROM lot_qt lqt
WHERE lqt.lot_s = l.id
AND lqt.lot_p IS NOT NULL
AND lqt.lot_quantity > 0::numeric
)
), pnl_lot_rows AS (
SELECT
purchase_pnl_lots.pnl_side,
purchase_pnl_lots.purchase_id,
purchase_pnl_lots.purchase_number,
purchase_pnl_lots.purchase_reference,
purchase_pnl_lots.purchase_line_id,
purchase_pnl_lots.purchase_line_finished,
purchase_pnl_lots.sale_id,
purchase_pnl_lots.sale_number,
purchase_pnl_lots.sale_reference,
purchase_pnl_lots.sale_line_id,
purchase_pnl_lots.sale_line_finished,
purchase_pnl_lots.valued_lot_id,
purchase_pnl_lots.valued_lot_type,
purchase_pnl_lots.lot_qt_id,
purchase_pnl_lots.pnl_quantity,
purchase_pnl_lots.quantity_unit,
purchase_pnl_lots.product_id,
purchase_pnl_lots.from_location_id,
purchase_pnl_lots.to_location_id,
purchase_pnl_lots.shipment_id,
purchase_pnl_lots.bl_date,
purchase_pnl_lots.delivery_date_from,
purchase_pnl_lots.delivery_date_to
FROM purchase_pnl_lots
UNION ALL
SELECT
sale_pnl_direct_lots.pnl_side,
sale_pnl_direct_lots.purchase_id,
sale_pnl_direct_lots.purchase_number,
sale_pnl_direct_lots.purchase_reference,
sale_pnl_direct_lots.purchase_line_id,
sale_pnl_direct_lots.purchase_line_finished,
sale_pnl_direct_lots.sale_id,
sale_pnl_direct_lots.sale_number,
sale_pnl_direct_lots.sale_reference,
sale_pnl_direct_lots.sale_line_id,
sale_pnl_direct_lots.sale_line_finished,
sale_pnl_direct_lots.valued_lot_id,
sale_pnl_direct_lots.valued_lot_type,
sale_pnl_direct_lots.lot_qt_id,
sale_pnl_direct_lots.pnl_quantity,
sale_pnl_direct_lots.quantity_unit,
sale_pnl_direct_lots.product_id,
sale_pnl_direct_lots.from_location_id,
sale_pnl_direct_lots.to_location_id,
sale_pnl_direct_lots.shipment_id,
sale_pnl_direct_lots.bl_date,
sale_pnl_direct_lots.delivery_date_from,
sale_pnl_direct_lots.delivery_date_to
FROM sale_pnl_direct_lots
UNION ALL
SELECT
sale_pnl_matched_virtual_lots.pnl_side,
sale_pnl_matched_virtual_lots.purchase_id,
sale_pnl_matched_virtual_lots.purchase_number,
sale_pnl_matched_virtual_lots.purchase_reference,
sale_pnl_matched_virtual_lots.purchase_line_id,
sale_pnl_matched_virtual_lots.purchase_line_finished,
sale_pnl_matched_virtual_lots.sale_id,
sale_pnl_matched_virtual_lots.sale_number,
sale_pnl_matched_virtual_lots.sale_reference,
sale_pnl_matched_virtual_lots.sale_line_id,
sale_pnl_matched_virtual_lots.sale_line_finished,
sale_pnl_matched_virtual_lots.valued_lot_id,
sale_pnl_matched_virtual_lots.valued_lot_type,
sale_pnl_matched_virtual_lots.lot_qt_id,
sale_pnl_matched_virtual_lots.pnl_quantity,
sale_pnl_matched_virtual_lots.quantity_unit,
sale_pnl_matched_virtual_lots.product_id,
sale_pnl_matched_virtual_lots.from_location_id,
sale_pnl_matched_virtual_lots.to_location_id,
sale_pnl_matched_virtual_lots.shipment_id,
sale_pnl_matched_virtual_lots.bl_date,
sale_pnl_matched_virtual_lots.delivery_date_from,
sale_pnl_matched_virtual_lots.delivery_date_to
FROM sale_pnl_matched_virtual_lots
UNION ALL
SELECT
sale_pnl_unmatched_lots.pnl_side,
sale_pnl_unmatched_lots.purchase_id,
sale_pnl_unmatched_lots.purchase_number,
sale_pnl_unmatched_lots.purchase_reference,
sale_pnl_unmatched_lots.purchase_line_id,
sale_pnl_unmatched_lots.purchase_line_finished,
sale_pnl_unmatched_lots.sale_id,
sale_pnl_unmatched_lots.sale_number,
sale_pnl_unmatched_lots.sale_reference,
sale_pnl_unmatched_lots.sale_line_id,
sale_pnl_unmatched_lots.sale_line_finished,
sale_pnl_unmatched_lots.valued_lot_id,
sale_pnl_unmatched_lots.valued_lot_type,
sale_pnl_unmatched_lots.lot_qt_id,
sale_pnl_unmatched_lots.pnl_quantity,
sale_pnl_unmatched_lots.quantity_unit,
sale_pnl_unmatched_lots.product_id,
sale_pnl_unmatched_lots.from_location_id,
sale_pnl_unmatched_lots.to_location_id,
sale_pnl_unmatched_lots.shipment_id,
sale_pnl_unmatched_lots.bl_date,
sale_pnl_unmatched_lots.delivery_date_from,
sale_pnl_unmatched_lots.delivery_date_to
FROM sale_pnl_unmatched_lots
), pnl_lot_rows_with_finished AS (
SELECT
pnl_lot_rows.pnl_side,
pnl_lot_rows.purchase_id,
pnl_lot_rows.purchase_number,
pnl_lot_rows.purchase_reference,
pnl_lot_rows.purchase_line_id,
pnl_lot_rows.purchase_line_finished,
pnl_lot_rows.sale_id,
pnl_lot_rows.sale_number,
pnl_lot_rows.sale_reference,
pnl_lot_rows.sale_line_id,
pnl_lot_rows.sale_line_finished,
pnl_lot_rows.valued_lot_id,
pnl_lot_rows.valued_lot_type,
pnl_lot_rows.lot_qt_id,
pnl_lot_rows.pnl_quantity,
pnl_lot_rows.quantity_unit,
pnl_lot_rows.product_id,
pnl_lot_rows.from_location_id,
pnl_lot_rows.to_location_id,
pnl_lot_rows.shipment_id,
pnl_lot_rows.bl_date,
pnl_lot_rows.delivery_date_from,
pnl_lot_rows.delivery_date_to,
CASE
WHEN pnl_lot_rows.pnl_side = 'purchase'::text THEN pnl_lot_rows.purchase_line_finished
WHEN pnl_lot_rows.pnl_side = 'sale'::text THEN pnl_lot_rows.sale_line_finished
ELSE false
END AS valued_line_finished,
CASE
WHEN pnl_lot_rows.valued_lot_type::text = 'virtual'::text
AND (
pnl_lot_rows.pnl_side = 'purchase'::text
AND pnl_lot_rows.purchase_line_finished IS TRUE
OR pnl_lot_rows.pnl_side = 'sale'::text
AND pnl_lot_rows.sale_line_finished IS TRUE
)
THEN true
ELSE false
END AS ignored_by_finished_rule
FROM pnl_lot_rows
)
SELECT
pnl_side,
purchase_id,
purchase_number,
purchase_reference,
purchase_line_id,
purchase_line_finished,
sale_id,
sale_number,
sale_reference,
sale_line_id,
sale_line_finished,
valued_lot_id,
valued_lot_type,
valued_line_finished,
ignored_by_finished_rule,
sum(pnl_quantity) AS pnl_quantity_raw,
sum(
CASE
WHEN ignored_by_finished_rule THEN 0::numeric
ELSE pnl_quantity
END
) AS pnl_quantity_after_finished_rule,
quantity_unit,
product_id,
from_location_id,
to_location_id,
shipment_id,
bl_date,
delivery_date_from,
delivery_date_to
FROM pnl_lot_rows_with_finished
GROUP BY
pnl_side,
purchase_id,
purchase_number,
purchase_reference,
purchase_line_id,
purchase_line_finished,
sale_id,
sale_number,
sale_reference,
sale_line_id,
sale_line_finished,
valued_lot_type,
valued_lot_id,
valued_line_finished,
ignored_by_finished_rule,
quantity_unit,
product_id,
from_location_id,
to_location_id,
shipment_id,
bl_date,
delivery_date_from,
delivery_date_to;

View File

@@ -0,0 +1,361 @@
WITH physical_shipment_lots AS (
SELECT l.lot_shipment_in AS shipment_id,
l.id AS lot_id,
l.lot_type,
l.line AS purchase_line_id,
l.sale_line AS sale_line_id,
qh.quantity AS shipment_quantity,
l.lot_unit_line AS shipment_unit_id
FROM lot_lot l
LEFT JOIN lot_qt_hist qh ON qh.lot = l.id AND qh.quantity_type = l.lot_state
WHERE l.lot_type::text = 'physic'::text AND l.lot_shipment_in IS NOT NULL
), virtual_shipment_lots AS (
SELECT q.lot_shipment_in AS shipment_id,
vp.id AS lot_id,
vp.lot_type,
vp.line AS purchase_line_id,
COALESCE(vp.sale_line, ps.sale_line) AS sale_line_id,
q.lot_quantity AS shipment_quantity,
q.lot_unit AS shipment_unit_id
FROM lot_qt q
JOIN lot_lot vp ON vp.id = q.lot_p AND vp.lot_type::text = 'virtual'::text
LEFT JOIN lot_lot ps ON ps.id = q.lot_s
WHERE q.lot_shipment_in IS NOT NULL AND NOT (EXISTS ( SELECT 1
FROM physical_shipment_lots pl
WHERE pl.shipment_id = q.lot_shipment_in AND pl.purchase_line_id = vp.line AND (pl.sale_line_id = COALESCE(vp.sale_line, ps.sale_line) OR COALESCE(vp.sale_line, ps.sale_line) IS NULL)))
), shipment_lots AS (
SELECT physical_shipment_lots.shipment_id,
physical_shipment_lots.lot_id,
physical_shipment_lots.lot_type,
physical_shipment_lots.purchase_line_id,
physical_shipment_lots.sale_line_id,
physical_shipment_lots.shipment_quantity,
physical_shipment_lots.shipment_unit_id
FROM physical_shipment_lots
UNION ALL
SELECT virtual_shipment_lots.shipment_id,
virtual_shipment_lots.lot_id,
virtual_shipment_lots.lot_type,
virtual_shipment_lots.purchase_line_id,
virtual_shipment_lots.sale_line_id,
virtual_shipment_lots.shipment_quantity,
virtual_shipment_lots.shipment_unit_id
FROM virtual_shipment_lots
), shipment_context AS (
SELECT shipment_lots.shipment_id,
shipment_lots.lot_id,
shipment_lots.purchase_line_id,
shipment_lots.sale_line_id,
shipment_lots.shipment_unit_id,
sum(COALESCE(shipment_lots.shipment_quantity, 0::numeric)) AS shipment_quantity
FROM shipment_lots
WHERE shipment_lots.shipment_id IS NOT NULL
GROUP BY shipment_lots.shipment_id, shipment_lots.lot_id, shipment_lots.purchase_line_id, shipment_lots.sale_line_id, shipment_lots.shipment_unit_id
), shipment_base AS (
SELECT shipment_context.shipment_id,
sum(shipment_context.shipment_quantity) AS shipment_quantity
FROM shipment_context
GROUP BY shipment_context.shipment_id
), shipment_fee_candidates AS (
SELECT f_1.id AS fee_id,
'Shipment'::text AS fee_source,
f_1.type AS fee_type,
CASE
WHEN f_1.type::text = 'ordered'::text THEN 1
WHEN f_1.type::text = 'scheduled'::text THEN 2
ELSE NULL::integer
END AS priority,
sb.shipment_id,
NULL::integer AS lot_id,
NULL::integer AS purchase_line_id,
NULL::integer AS sale_line_id,
f_1.product AS product_id,
f_1.supplier AS supplier_id,
f_1.mode AS packaging,
f_1.p_r AS pay_or_rec,
f_1.state,
f_1.weight_type,
COALESCE(f_1.quantity, 0::numeric) AS requested_quantity,
sb.shipment_quantity,
f_1.price AS fee_price,
f_1.currency AS currency_id,
f_1.unit AS unit_id,
CASE
WHEN upper(f_1.p_r::text) = 'REC'::text THEN 1
ELSE '-1'::integer
END AS sign_multiplier,
NULL::text AS forced_cost_group
FROM shipment_base sb
JOIN fee_fee f_1 ON f_1.shipment_in = sb.shipment_id
WHERE f_1.type::text = ANY (ARRAY['ordered'::character varying::text, 'scheduled'::character varying::text])
), shipment_fee_allocated AS (
SELECT c.fee_id,
c.fee_source,
c.fee_type,
c.priority,
c.shipment_id,
c.lot_id,
c.purchase_line_id,
c.sale_line_id,
c.product_id,
c.supplier_id,
c.packaging,
c.pay_or_rec,
c.state,
c.weight_type,
c.requested_quantity,
c.shipment_quantity,
c.fee_price,
c.currency_id,
c.unit_id,
c.sign_multiplier,
c.forced_cost_group,
LEAST(GREATEST(c.requested_quantity, 0::numeric), GREATEST(c.shipment_quantity - COALESCE(sum(c.requested_quantity) OVER (PARTITION BY c.shipment_id, c.product_id, c.supplier_id ORDER BY c.priority, c.fee_id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0::numeric), 0::numeric)) AS allocated_quantity
FROM shipment_fee_candidates c
), shipment_fee_coverage AS (
SELECT shipment_fee_allocated.shipment_id,
shipment_fee_allocated.product_id,
shipment_fee_allocated.supplier_id,
max(shipment_fee_allocated.shipment_quantity) AS shipment_quantity,
sum(shipment_fee_allocated.allocated_quantity) AS allocated_quantity
FROM shipment_fee_allocated
GROUP BY shipment_fee_allocated.shipment_id, shipment_fee_allocated.product_id, shipment_fee_allocated.supplier_id
), contract_budgeted_fee_candidates AS (
SELECT f_1.id AS fee_id,
'Purchase Contract'::text AS fee_source,
f_1.type AS fee_type,
3 AS priority,
sc.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
f_1.product AS product_id,
f_1.supplier AS supplier_id,
f_1.mode AS packaging,
f_1.p_r AS pay_or_rec,
f_1.state,
f_1.weight_type,
sc.shipment_quantity,
f_1.price AS fee_price,
f_1.currency AS currency_id,
COALESCE(f_1.unit, sc.shipment_unit_id) AS unit_id,
CASE
WHEN upper(f_1.p_r::text) = 'REC'::text THEN '-1'::integer
ELSE 1
END AS sign_multiplier,
NULL::text AS forced_cost_group
FROM shipment_context sc
JOIN fee_fee f_1 ON f_1.line = sc.purchase_line_id
WHERE f_1.type::text = 'budgeted'::text AND sc.purchase_line_id IS NOT NULL
UNION ALL
SELECT f_1.id AS fee_id,
'Sale Contract'::text AS fee_source,
f_1.type AS fee_type,
3 AS priority,
sc.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
f_1.product AS product_id,
f_1.supplier AS supplier_id,
f_1.mode AS packaging,
f_1.p_r AS pay_or_rec,
f_1.state,
f_1.weight_type,
sc.shipment_quantity,
f_1.price AS fee_price,
f_1.currency AS currency_id,
COALESCE(f_1.unit, sc.shipment_unit_id) AS unit_id,
CASE
WHEN upper(f_1.p_r::text) = 'REC'::text THEN 1
ELSE '-1'::integer
END AS sign_multiplier,
NULL::text AS forced_cost_group
FROM shipment_context sc
JOIN fee_fee f_1 ON f_1.sale_line = sc.sale_line_id
WHERE f_1.type::text = 'budgeted'::text AND sc.sale_line_id IS NOT NULL
), contract_budgeted_allocated AS (
SELECT c.fee_id,
c.fee_source,
c.fee_type,
c.priority,
c.shipment_id,
c.lot_id,
c.purchase_line_id,
c.sale_line_id,
c.product_id,
c.supplier_id,
c.packaging,
c.pay_or_rec,
c.state,
c.weight_type,
c.shipment_quantity,
c.fee_price,
c.currency_id,
c.unit_id,
c.sign_multiplier,
c.forced_cost_group,
LEAST(c.shipment_quantity, GREATEST(sum(c.shipment_quantity) OVER (PARTITION BY c.shipment_id, c.product_id, c.supplier_id, c.fee_id) - COALESCE(fc.allocated_quantity, 0::numeric) - COALESCE(sum(c.shipment_quantity) OVER (PARTITION BY c.shipment_id, c.product_id, c.supplier_id, c.fee_id ORDER BY c.lot_id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0::numeric), 0::numeric)) AS allocated_quantity
FROM contract_budgeted_fee_candidates c
LEFT JOIN shipment_fee_coverage fc ON fc.shipment_id = c.shipment_id AND fc.product_id = c.product_id AND fc.supplier_id = c.supplier_id
), price_rows AS (
SELECT NULL::integer AS fee_id,
'Purchase Price'::text AS fee_source,
'price'::text AS fee_type,
4 AS priority,
sc.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
pl.product AS product_id,
NULL::integer AS supplier_id,
NULL::character varying AS packaging,
NULL::character varying AS pay_or_rec,
NULL::character varying AS state,
NULL::character varying AS weight_type,
sc.shipment_quantity AS allocated_quantity,
pl.unit_price AS fee_price,
pl.currency_ AS currency_id,
COALESCE(pl.unit, sc.shipment_unit_id) AS unit_id,
1 AS sign_multiplier,
'PurchasePrice'::text AS forced_cost_group
FROM shipment_context sc
JOIN purchase_line pl ON pl.id = sc.purchase_line_id
WHERE sc.purchase_line_id IS NOT NULL
UNION ALL
SELECT NULL::integer AS fee_id,
'Sale Price'::text AS fee_source,
'price'::text AS fee_type,
5 AS priority,
sc.shipment_id,
sc.lot_id,
sc.purchase_line_id,
sc.sale_line_id,
sl.product AS product_id,
NULL::integer AS supplier_id,
NULL::character varying AS packaging,
NULL::character varying AS pay_or_rec,
NULL::character varying AS state,
NULL::character varying AS weight_type,
sc.shipment_quantity AS allocated_quantity,
sl.unit_price AS fee_price,
ss.currency AS currency_id,
COALESCE(sl.unit, sc.shipment_unit_id) AS unit_id,
'-1'::integer AS sign_multiplier,
'SalePrice'::text AS forced_cost_group
FROM shipment_context sc
JOIN sale_line sl ON sl.id = sc.sale_line_id
JOIN sale_sale ss ON ss.id = sl.sale
WHERE sc.sale_line_id IS NOT NULL
), final_fees AS (
SELECT shipment_fee_allocated.fee_id,
shipment_fee_allocated.fee_source,
shipment_fee_allocated.fee_type,
shipment_fee_allocated.priority,
shipment_fee_allocated.shipment_id,
shipment_fee_allocated.lot_id,
shipment_fee_allocated.purchase_line_id,
shipment_fee_allocated.sale_line_id,
shipment_fee_allocated.product_id,
shipment_fee_allocated.supplier_id,
shipment_fee_allocated.packaging,
shipment_fee_allocated.pay_or_rec,
shipment_fee_allocated.state,
shipment_fee_allocated.weight_type,
shipment_fee_allocated.allocated_quantity AS fee_quantity,
shipment_fee_allocated.fee_price,
shipment_fee_allocated.currency_id,
shipment_fee_allocated.unit_id,
shipment_fee_allocated.sign_multiplier,
shipment_fee_allocated.forced_cost_group
FROM shipment_fee_allocated
WHERE shipment_fee_allocated.allocated_quantity > 0::numeric
UNION ALL
SELECT contract_budgeted_allocated.fee_id,
contract_budgeted_allocated.fee_source,
contract_budgeted_allocated.fee_type,
contract_budgeted_allocated.priority,
contract_budgeted_allocated.shipment_id,
contract_budgeted_allocated.lot_id,
contract_budgeted_allocated.purchase_line_id,
contract_budgeted_allocated.sale_line_id,
contract_budgeted_allocated.product_id,
contract_budgeted_allocated.supplier_id,
contract_budgeted_allocated.packaging,
contract_budgeted_allocated.pay_or_rec,
contract_budgeted_allocated.state,
contract_budgeted_allocated.weight_type,
contract_budgeted_allocated.allocated_quantity AS fee_quantity,
contract_budgeted_allocated.fee_price,
contract_budgeted_allocated.currency_id,
contract_budgeted_allocated.unit_id,
contract_budgeted_allocated.sign_multiplier,
contract_budgeted_allocated.forced_cost_group
FROM contract_budgeted_allocated
WHERE contract_budgeted_allocated.allocated_quantity > 0::numeric
UNION ALL
SELECT price_rows.fee_id,
price_rows.fee_source,
price_rows.fee_type,
price_rows.priority,
price_rows.shipment_id,
price_rows.lot_id,
price_rows.purchase_line_id,
price_rows.sale_line_id,
price_rows.product_id,
price_rows.supplier_id,
price_rows.packaging,
price_rows.pay_or_rec,
price_rows.state,
price_rows.weight_type,
price_rows.allocated_quantity AS fee_quantity,
price_rows.fee_price,
price_rows.currency_id,
price_rows.unit_id,
price_rows.sign_multiplier,
price_rows.forced_cost_group
FROM price_rows
WHERE price_rows.allocated_quantity > 0::numeric
)
SELECT f.shipment_id AS "intShipmentId",
f.lot_id AS "intLotId",
COALESCE(f.fee_id, 0) AS "intFeeId",
f.fee_source AS "strFeeSource",
f.fee_type AS "strFeeType",
f.priority AS "intPriority",
COALESCE(f.purchase_line_id, 0) AS "intPurchaseLineId",
COALESCE(f.sale_line_id, 0) AS "intSaleLineId",
COALESCE(f.product_id, 0) AS "intProductId",
COALESCE(p.code, ''::character varying) AS "strFee",
COALESCE(f.supplier_id, 0) AS "intSupplierId",
COALESCE(sup.name, ''::character varying) AS "strSupplier",
COALESCE(f.packaging, ''::character varying) AS "strPackaging",
COALESCE(f.pay_or_rec, ''::character varying) AS "strPayOrRec",
COALESCE(f.state, ''::character varying) AS "strState",
CASE
WHEN upper(f.weight_type::text) = 'BRUT'::text THEN 'Gross'::text
ELSE 'Net'::text
END AS "strWeighingType",
f.fee_quantity AS "dblQuantity",
f.fee_price AS "dblPrice",
COALESCE(cur.name, ''::character varying) AS "strCurrency",
COALESCE(uom.name, 'Mt'::character varying) AS "strUnit",
f.fee_quantity * f.fee_price AS "dblAmount",
COALESCE(f.forced_cost_group,
CASE
WHEN upper(p.code::text) ~~ '%FREIGHT%'::text THEN 'Freight'::text
WHEN upper(p.code::text) ~~ '%PROFIT SHARING%'::text THEN 'Profit Sharing'::text
ELSE 'Other Costs'::text
END) AS "strCostGroup",
CASE
WHEN f.pay_or_rec::text = 'pay'::text THEN - 1::numeric
WHEN f.pay_or_rec::text = 'rec'::text THEN 1::numeric
WHEN f.fee_source = 'Purchase Price'::text AND f.fee_type::text = 'price'::text THEN - 1::numeric
WHEN f.fee_source = 'Sale Price'::text AND f.fee_type::text = 'price'::text THEN 1::numeric
ELSE - 1::numeric
END AS "intSignMultiplier"
FROM final_fees f
JOIN product_product p ON p.id = f.product_id
LEFT JOIN party_party sup ON sup.id = f.supplier_id
LEFT JOIN currency_currency cur ON cur.id = f.currency_id
LEFT JOIN product_uom uom ON uom.id = f.unit_id;

View File

@@ -1,7 +1,7 @@
# This file is part of Tradon. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import ModelSQL, ModelView, fields
from trytond.model import ModelSQL, ModelView, fields, tree
from trytond.model import sequence_ordered
from trytond.pool import Pool
from trytond.pyson import Eval, Bool
@@ -184,7 +184,7 @@ class FacilityCovenant(ModelSQL, ModelView):
# Facility Limit (Global Limit + Sub-Limits in one model)
# ---------------------------------------------------------------------------
class FacilityLimit(ModelSQL, ModelView):
class FacilityLimit(tree(), ModelSQL, ModelView):
'Facility Limit'
__name__ = 'trade_finance.facility_limit'
_rec_name = 'name'
@@ -194,24 +194,41 @@ class FacilityLimit(ModelSQL, ModelView):
states={'readonly': Bool(Eval('parent'))},
depends=['parent'])
parent = fields.Many2One('trade_finance.facility_limit', 'Parent Limit',
left='left', right='right',
ondelete='RESTRICT',
domain=[('facility', '=', Eval('facility'))],
depends=['facility'],
help='Leave empty for Global Limit (root node)')
children = fields.One2Many('trade_finance.facility_limit', 'parent',
'Sub-Limits')
left = fields.Integer('Left', required=True)
right = fields.Integer('Right', required=True)
name = fields.Char('Name', required=True)
alternative_name = fields.Char('Limit Alternative Name')
financing_type = fields.Many2One('trade_finance.financing_type',
'Financing Type', ondelete='RESTRICT')
currency = fields.Many2One('currency.currency', 'Currency',
ondelete='RESTRICT',
states={'readonly': Bool(Eval('parent'))},
depends=['parent'],
help='Currency of the global limit. Sub-limits inherit it from '
'their parent.')
amount = fields.Numeric('Amount', digits=(16, 2), required=True)
tenor = fields.Integer('Tenor (days)',
help='Maximum duration of financing from drawdown to repayment')
date_from = fields.Date('Valid From')
date_to = fields.Date('Valid To')
sequence = fields.Integer('Sequence')
notes = fields.Text('Notes')
_order = [('sequence', 'ASC'), ('id', 'ASC')]
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cls._rebuild_tree('parent', None, 0)
haircuts = fields.One2Many('trade_finance.facility_limit_haircut', 'limit',
'Haircuts')
currencies = fields.One2Many('trade_finance.facility_limit_currency',
@@ -230,6 +247,38 @@ class FacilityLimit(ModelSQL, ModelView):
def default_sequence():
return 10
@staticmethod
def default_left():
return 0
@staticmethod
def default_right():
return 0
@fields.depends('facility', 'currency', 'date_from', 'date_to', 'parent',
'_parent_parent.facility', '_parent_parent.currency',
'_parent_parent.date_from', '_parent_parent.date_to')
def on_change_parent(self):
if self.parent:
self.facility = self.parent.facility
self.currency = self.parent.currency
if not self.date_from:
self.date_from = self.parent.date_from
if not self.date_to:
self.date_to = self.parent.date_to
@fields.depends('facility', 'currency', 'date_from', 'date_to', 'parent',
'_parent_facility.currency', '_parent_facility.date_from',
'_parent_facility.date_to')
def on_change_facility(self):
if self.facility and not self.parent:
if not self.currency:
self.currency = self.facility.currency
if not self.date_from:
self.date_from = self.facility.date_from
if not self.date_to:
self.date_to = self.facility.date_to
@classmethod
def create(cls, vlist):
vlist = [v.copy() for v in vlist]
@@ -237,13 +286,73 @@ class FacilityLimit(ModelSQL, ModelView):
if values.get('parent') and not values.get('facility'):
parent = cls(values['parent'])
values['facility'] = parent.facility.id
if values.get('parent'):
parent = cls(values['parent'])
values['currency'] = (
parent.currency.id if parent.currency else None)
values.setdefault('date_from', parent.date_from)
values.setdefault('date_to', parent.date_to)
elif values.get('facility'):
facility = Pool().get('trade_finance.facility')(
values['facility'])
values.setdefault('currency',
facility.currency.id if facility.currency else None)
values.setdefault('date_from', facility.date_from)
values.setdefault('date_to', facility.date_to)
return super().create(vlist)
@classmethod
def write(cls, *args):
args = list(args)
actions = iter(args)
new_args = []
check_descendants = []
sync_currency = []
for limits, values in zip(actions, actions):
values = values.copy()
if values.get('parent'):
parent = cls(values['parent'])
values['facility'] = parent.facility.id
values['currency'] = (
parent.currency.id if parent.currency else None)
if {'parent', 'currency'} & set(values):
sync_currency.extend(limits)
if {'parent', 'currency', 'date_from', 'date_to'} & set(values):
check_descendants.extend(limits)
new_args.extend((limits, values))
result = super().write(*new_args)
cls._sync_descendant_currencies(sync_currency)
cls._check_descendants(check_descendants)
return result
@classmethod
def _sync_descendant_currencies(cls, limits):
for limit in limits:
limit = cls(limit.id)
if not limit.currency:
continue
descendants = cls.search([('parent', 'child_of', [limit.id])])
if descendants:
super(FacilityLimit, cls).write(
descendants, {'currency': limit.currency.id})
@classmethod
def _check_descendants(cls, limits):
descendants = []
for limit in limits:
descendants.extend(
cls.search([('parent', 'child_of', [limit.id])]))
if descendants:
cls.validate(descendants)
@classmethod
def validate(cls, limits):
super().validate(limits)
for limit in limits:
limit.check_single_root()
limit.check_required_values()
limit.check_parent_consistency()
limit.check_dates_within_parent()
limit.check_amount_vs_parent()
limit.check_children_amounts()
@@ -260,6 +369,49 @@ class FacilityLimit(ModelSQL, ModelView):
f"Limit ('{roots[0].name}'). Only one root limit is "
f"allowed per facility.")
def check_required_values(self):
if not self.currency:
raise UserError(
f"Limit '{self.name}' must define or inherit a currency.")
if not self.date_from or not self.date_to:
raise UserError(
f"Limit '{self.name}' must define valid from and valid to "
f"dates.")
if self.date_from > self.date_to:
raise UserError(
f"Limit '{self.name}' valid from date cannot be after "
f"its valid to date.")
def check_parent_consistency(self):
if not self.parent:
return
if self.facility != self.parent.facility:
raise UserError(
f"Sub-limit '{self.name}' must belong to the same facility "
f"as parent limit '{self.parent.name}'.")
if self.currency != self.parent.currency:
raise UserError(
f"Sub-limit '{self.name}' must use the same currency as "
f"parent limit '{self.parent.name}'.")
def check_dates_within_parent(self):
if self.date_from and self.date_to and self.date_from > self.date_to:
raise UserError(
f"Limit '{self.name}' valid from date cannot be after its "
f"valid to date.")
if not self.parent:
return
if (self.date_from and self.parent.date_from
and self.date_from < self.parent.date_from):
raise UserError(
f"Sub-limit '{self.name}' valid from date cannot be before "
f"parent limit '{self.parent.name}' valid from date.")
if (self.date_to and self.parent.date_to
and self.date_to > self.parent.date_to):
raise UserError(
f"Sub-limit '{self.name}' valid to date cannot be after "
f"parent limit '{self.parent.name}' valid to date.")
def check_amount_vs_parent(self):
if self.parent and self.amount > self.parent.amount:
raise UserError(

View File

@@ -0,0 +1,2 @@
# This file is part of Tradon. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,117 @@
# This file is part of Tradon. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from datetime import date
from decimal import Decimal
from trytond.exceptions import UserError
from trytond.modules.currency.tests import create_currency
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
class TradeFinanceTestCase(ModuleTestCase):
'Test Trade Finance module'
module = 'trade_finance'
def create_facility(self):
pool = Pool()
Bank = pool.get('bank')
Facility = pool.get('trade_finance.facility')
Party = pool.get('party.party')
currency = create_currency('USD')
party = Party(name='Test Bank')
party.save()
bank = Bank(party=party)
bank.save()
facility = Facility(
name='Test Facility',
tfe=bank,
currency=currency,
commitment_status='committed',
date_from=date(2026, 1, 1),
date_to=date(2026, 12, 31))
facility.save()
return facility, currency
@with_transaction()
def test_sublimit_inherits_currency_and_dates(self):
'Test sublimit inherits currency and dates from parent'
pool = Pool()
Limit = pool.get('trade_finance.facility_limit')
facility, currency = self.create_facility()
root, = Limit.create([{
'facility': facility.id,
'name': 'Global limit',
'currency': currency.id,
'amount': Decimal('100.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
child, = Limit.create([{
'parent': root.id,
'name': 'Trading limit',
'amount': Decimal('50.00'),
}])
self.assertEqual(child.facility, facility)
self.assertEqual(child.currency, currency)
self.assertEqual(child.date_from, root.date_from)
self.assertEqual(child.date_to, root.date_to)
@with_transaction()
def test_sublimit_dates_must_stay_within_parent(self):
'Test sublimit dates stay within parent dates'
pool = Pool()
Limit = pool.get('trade_finance.facility_limit')
facility, currency = self.create_facility()
root, = Limit.create([{
'facility': facility.id,
'name': 'Global limit',
'currency': currency.id,
'amount': Decimal('100.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
with self.assertRaises(UserError):
Limit.create([{
'parent': root.id,
'name': 'Late limit',
'amount': Decimal('50.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2027, 1, 1),
}])
@with_transaction()
def test_currency_change_cascades_to_sublimits(self):
'Test root currency changes cascade to sublimits'
pool = Pool()
Limit = pool.get('trade_finance.facility_limit')
facility, currency = self.create_facility()
other_currency = create_currency('EUR')
root, = Limit.create([{
'facility': facility.id,
'name': 'Global limit',
'currency': currency.id,
'amount': Decimal('100.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
child, = Limit.create([{
'parent': root.id,
'name': 'Trading limit',
'amount': Decimal('50.00'),
}])
Limit.write([root], {'currency': other_currency.id})
child = Limit(child.id)
self.assertEqual(child.currency, other_currency)
del ModuleTestCase

View File

@@ -30,9 +30,10 @@
<field name="description" colspan="5"/>
</group>
<notebook colspan="6">
<page string="Limits" id="limits">
<page string="Global Limit" id="limits">
<field name="limits" colspan="6"
domain="[('parent', '=', None)]"/>
domain="[('parent', '=', None)]"
view_ids="trade_finance.facility_limit_view_tree,trade_finance.facility_limit_view_form"/>
</page>
<page string="Currencies" id="currencies">
<field name="currencies" colspan="6"/>

View File

@@ -6,8 +6,14 @@
<field name="alternative_name" colspan="3"/>
<label name="financing_type"/>
<field name="financing_type" colspan="3"/>
<label name="currency"/>
<field name="currency"/>
<label name="amount"/>
<field name="amount"/>
<label name="date_from"/>
<field name="date_from"/>
<label name="date_to"/>
<field name="date_to"/>
<label name="tenor"/>
<field name="tenor"/>
<label name="sequence"/>
@@ -17,10 +23,13 @@
<field name="parent" colspan="3"/>
<label name="facility"/>
<field name="facility" colspan="3"/>
<label name="notes"/>
<field name="notes" colspan="3"/>
</group>
<notebook colspan="4">
<page string="Sub-Limits" id="children">
<field name="children" colspan="4"/>
<field name="children" colspan="4"
view_ids="trade_finance.facility_limit_view_tree,trade_finance.facility_limit_view_form"/>
</page>
<page string="Haircuts" id="haircuts">
<field name="haircuts" colspan="4"/>

View File

@@ -1,7 +1,11 @@
<tree expand="1">
<field name="name"/>
<field name="parent"/>
<tree expand="1" keyword_open="1">
<field name="name" expand="2"/>
<field name="parent" tree_invisible="1"/>
<field name="children" tree_invisible="1"/>
<field name="financing_type"/>
<field name="currency"/>
<field name="amount"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="tenor"/>
</tree>