Compare commits

...

55 Commits

Author SHA1 Message Date
AzureAD\SylvainDUVERNAY
69d10d322b Import Price - New Excel file structure 2026-06-01 22:06:28 +02:00
AzureAD\SylvainDUVERNAY
77fe2f0589 Import Price - New Excel file structure 2026-06-01 21:43:46 +02:00
AzureAD\SylvainDUVERNAY
3535567376 ITSA Workflow 2026-05-31 15:05:15 +02:00
AzureAD\SylvainDUVERNAY
54f2e1a934 ITSA Workflow 2026-05-31 14:53:29 +02:00
AzureAD\SylvainDUVERNAY
6f29d4cf86 ITSA Workflow 2026-05-31 13:59:02 +02:00
AzureAD\SylvainDUVERNAY
0d05e3b22d ITSA Workflow 2026-05-30 15:33:23 +02:00
AzureAD\SylvainDUVERNAY
093be8ebdb ITSA Workflow 2026-05-30 15:27:48 +02:00
AzureAD\SylvainDUVERNAY
b80385ebc0 ITSA Workflow 2026-05-30 15:18:58 +02:00
AzureAD\SylvainDUVERNAY
e22fc67a3c ITSA Workflow 2026-05-30 14:42:57 +02:00
AzureAD\SylvainDUVERNAY
2a8e059e3b ITSA Workflow 2026-05-30 14:26:39 +02:00
AzureAD\SylvainDUVERNAY
f71ad2ac69 Fix process documentation navigation 2026-05-30 14:13:12 +02:00
AzureAD\SylvainDUVERNAY
34b221deae Tradon Processes 2026-05-30 14:02:57 +02:00
AzureAD\SylvainDUVERNAY
089acfa5e3 Render Tradon processes in iframe viewer 2026-05-30 13:28:17 +02:00
AzureAD\SylvainDUVERNAY
2e373b2ce6 Fix Tradon processes content rendering 2026-05-29 14:12:39 +02:00
AzureAD\SylvainDUVERNAY
2db551c2f4 Open Tradon processes inside Tryton 2026-05-29 13:58:08 +02:00
AzureAD\SylvainDUVERNAY
d35d88271d Open Tradon processes inside Tryton 2026-05-29 13:52:57 +02:00
AzureAD\SylvainDUVERNAY
9facb61cae Open Tradon processes inside Tryton 2026-05-29 12:57:48 +02:00
AzureAD\SylvainDUVERNAY
6441bbb31d Fix Tradon processes menu URL 2026-05-29 12:48:43 +02:00
AzureAD\SylvainDUVERNAY
092fe138c8 Add Tradon processes help menu 2026-05-29 12:41:57 +02:00
AzureAD\SylvainDUVERNAY
381197b7bb Add trade finance ledger foundation 2026-05-28 22:31:06 +02:00
AzureAD\SylvainDUVERNAY
8542bfeabb Fix facility limit hierarchy display 2026-05-25 11:58:11 +02:00
AzureAD\SylvainDUVERNAY
0155e99815 Filter limits One2Many to root records only to fix tree double-display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 11:47:23 +02:00
AzureAD\SylvainDUVERNAY
d835f2521c Fix invalid expand attribute on facility_limit tree view
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 11:40:34 +02:00
AzureAD\SylvainDUVERNAY
bdfc7f22e0 Remove fx fields from Facility; fix facility_limit tree and parent field
- Remove fx_feeder and fx_haircut_pct fields from Facility model and form view
- Add field_childs to facility_limit tree view for hierarchical display
- Set parent field as readonly in facility_limit form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 11:37:47 +02:00
AzureAD\SylvainDUVERNAY
181de17b89 Trade Finance + Other changes 2026-05-25 11:17:18 +02:00
AzureAD\SylvainDUVERNAY
452e9e2632 Shipment fees allocation 2026-05-10 16:16:09 +02:00
AzureAD\SylvainDUVERNAY
9e33a577e8 Project organisation 2026-05-08 08:56:26 +02:00
AzureAD\SylvainDUVERNAY
8cb570126a Project organization and structure 2026-05-08 08:51:51 +02:00
AzureAD\SylvainDUVERNAY
60d53b2d67 Project documentation and organisation 2026-05-07 15:21:25 +02:00
AzureAD\SylvainDUVERNAY
7f2e61c059 Record market price import backlog commit 2026-05-07 15:02:44 +02:00
AzureAD\SylvainDUVERNAY
eaae2e5b40 Document rules and enforce price value uniqueness 2026-05-07 15:02:15 +02:00
AzureAD\SylvainDUVERNAY
8fb6d681a0 Import Prices 2026-05-06 14:47:57 +02:00
AzureAD\SylvainDUVERNAY
17934fe210 Increase import prices result height 2026-05-06 14:31:48 +02:00
AzureAD\SylvainDUVERNAY
ef54b5d94b Import Prices 2026-05-06 14:21:51 +02:00
AzureAD\SylvainDUVERNAY
3a0ea871c2 ITSA - Import Prices 2026-05-05 16:43:29 +02:00
AzureAD\SylvainDUVERNAY
1c8abc7c1e Trade Finance 2026-04-12 17:22:25 +02:00
AzureAD\SylvainDUVERNAY
37bf6ba23b Trade Finance 2026-04-12 17:15:08 +02:00
AzureAD\SylvainDUVERNAY
2e649aa61b Commit 2026-04-12 17:06:26 +02:00
AzureAD\SylvainDUVERNAY
735a72d23e Trade Finance 2026-04-12 16:58:45 +02:00
AzureAD\SylvainDUVERNAY
ebf9b6c495 Trade Finance 2026-04-12 16:47:59 +02:00
AzureAD\SylvainDUVERNAY
13d26ac41b Trade Finance 2026-04-12 16:36:08 +02:00
AzureAD\SylvainDUVERNAY
b829b11791 Trade Finance - Commit 2026-04-12 14:41:12 +02:00
AzureAD\SylvainDUVERNAY
4a056ef402 Change on Facility definition 2026-04-07 21:53:10 +02:00
AzureAD\SylvainDUVERNAY
da65da79c0 Trade Finance - Facility - Limit ordering 2026-03-31 22:06:58 +02:00
AzureAD\SylvainDUVERNAY
aa6b3fb9ad Trade Finance - TF Facility - Order limits 2026-03-31 21:55:34 +02:00
AzureAD\SylvainDUVERNAY
b1dd118628 Trade Finance - Facility - Improve Limits table layout 2026-03-31 21:39:56 +02:00
AzureAD\SylvainDUVERNAY
f1f9d157cc Trade Finance - Facility - Fix error on Save 2026-03-31 16:50:45 +02:00
AzureAD\SylvainDUVERNAY
283b71fda9 Trade Finance - Sub Limit 2026-03-31 14:14:05 +02:00
AzureAD\SylvainDUVERNAY
f27dd5620e Trade Finance - Adjustment on sub-limit 2026-03-31 14:10:43 +02:00
AzureAD\SylvainDUVERNAY
143f59c62e Trade Finance - Facility Fix 3 2026-03-31 13:37:42 +02:00
AzureAD\SylvainDUVERNAY
be6b6517a5 Trade Finance - Facility Fix 2 2026-03-31 13:34:31 +02:00
AzureAD\SylvainDUVERNAY
d96973310b Trade Finance Facility - Fix 1 2026-03-31 13:31:33 +02:00
AzureAD\SylvainDUVERNAY
39278c4483 Trade Finance - Facility 2026-03-31 13:22:18 +02:00
AzureAD\SylvainDUVERNAY
4534ad86f1 Trade Finance - Fix 2026-03-31 12:24:17 +02:00
AzureAD\SylvainDUVERNAY
10e8e5be9b Trade Finance - Initial Commit 2026-03-31 12:14:51 +02:00
345 changed files with 20414 additions and 190 deletions

View File

@@ -0,0 +1,17 @@
{
"permissions": {
"allow": [
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules)",
"Bash(ls -d */)",
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules/purchase_trade)",
"Bash(ls -1d */)",
"Bash(for f:*)",
"Bash(do echo:*)",
"Read(//c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/**)",
"Bash(done)",
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules/purchase_trade/view)",
"Bash(ls -1 *.xml)",
"Bash(py --version)"
]
}
}

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
deployment/vps-TradonDev_Instructions.md
deployment/vps/46.202.173.47-credentials.md

View File

@@ -0,0 +1,9 @@
# ITSA_Tradon_Prod
## PostgreSQL Database
- Server: `72.61.163.139`
- Port: `5433`
- User name: `postgres`
- Password: `dsproject`
- Database name: `tradon`

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;

5
deployment/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Deployment Notes
- Runbook onboarding SSH: `deployment/runbooks/vps-onboarding.md`
- VPS 46.202.173.47 credentials: `deployment/vps/46.202.173.47-credentials.md`
- VPS 46.202.173.47 quickstart: `deployment/vps/46.202.173.47-quickstart.md`

View File

@@ -0,0 +1,57 @@
# Procedure - Ajouter un nouveau VPS (SSH)
Date de reference: 2026-04-07
## 1) Preparation locale (Windows)
1. Creer le dossier SSH local si absent:
`New-Item -ItemType Directory -Force $env:USERPROFILE\.ssh`
2. Generer une cle dediee VPS:
`ssh-keygen -t ed25519 -C "vps-deploy" -f $env:USERPROFILE\.ssh\vps_deploy_key`
3. Lire la cle publique:
`Get-Content $env:USERPROFILE\.ssh\vps_deploy_key.pub`
## 2) Installer la cle sur le VPS
1. Se connecter au VPS avec mot de passe (premiere fois):
`ssh <user>@<ip_vps>`
2. Sur le VPS, preparer le dossier SSH:
`mkdir -p ~/.ssh`
`chmod 700 ~/.ssh`
3. Ajouter la cle publique (une ligne complete):
`echo "ssh-ed25519 ... vps-deploy" >> ~/.ssh/authorized_keys`
`chmod 600 ~/.ssh/authorized_keys`
## 3) Tester la connexion par cle
Depuis Windows:
`ssh -i $env:USERPROFILE\.ssh\vps_deploy_key <user>@<ip_vps>`
## 4) Test operationnel minimal
Creer un dossier distant:
`ssh -i $env:USERPROFILE\.ssh\vps_deploy_key <user>@<ip_vps> "mkdir -p ~/test_codex_deploy && ls -ld ~/test_codex_deploy"`
## 5) Durcissement recommande
- Desactiver l'authentification par mot de passe apres validation de la cle.
- Utiliser une cle dediee par environnement (dev/staging/prod).
- Documenter user + IP + chemin de cle dans une fiche VPS separee.
## 6) Note importante - Contrainte sandbox Codex
- Si une commande SSH/SCP echoue avec un message proche de:
- `Identity file ... not accessible: Permission denied`
- Cause probable:
- la session est en sandbox et ne peut pas lire la cle locale dans
`C:\Users\<user>\.ssh\...`.
- Action:
- relancer la commande en mode `require_escalated` (hors sandbox) pour
autoriser l'acces a la cle locale.
- Exemple observe le 2026-04-07:
- creation du dossier `/root/test` sur `46.202.173.47` reussie uniquement
apres escalation.

View File

@@ -0,0 +1,39 @@
# Commandes Rapides - VPS 46.202.173.47
Date de reference: 2026-04-07
## Cles SSH par utilisateur
| Utilisateur | Fichier cle locale |
|---|---|
| Laurent Barontini | `$env:USERPROFILE\.ssh\vps_deploy_key` |
| Sylvain Duvernay | `$env:USERPROFILE\.ssh\id_ed25519` |
> Les commandes ci-dessous utilisent `id_ed25519` (Sylvain Duvernay).
## 1) Test SSH
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47 "echo ok"`
## 2) Creer un dossier test distant
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47 "mkdir -p ~/test_codex_deploy && ls -ld ~/test_codex_deploy"`
## 3) Lister home distant
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47 "ls -la ~"`
## 4) Copier un fichier local vers le VPS
`scp -i $env:USERPROFILE\.ssh\id_ed25519 .\local.txt <user>@46.202.173.47:~/local.txt`
## 5) Recuperer un fichier du VPS
`scp -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47:~/remote.txt .\remote.txt`
## 6) Depannage sandbox (Codex)
- Symptome:
- `Identity file ... not accessible: Permission denied`
- Correctif:
- relancer la commande SSH/SCP en mode escalade (`require_escalated`).

View File

@@ -0,0 +1,3 @@
Serveur: 'VPS-62.72.36.116'
Alias Name: 'VPS DEV'
IP Address:'62.72.36.116'

View File

@@ -233,6 +233,7 @@
<value>email</value>
<value>float</value>
<value>html</value>
<value>html_viewer</value>
<value>image</value>
<value>integer</value>
<value>many2many</value>

Binary file not shown.

View File

@@ -3,6 +3,9 @@
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_price_value_unique">
<field name="text">Only one price value is allowed for each price index and price date.</field>
</record>
<record model="ir.message" id="msg_party_code_unique">
<field name="text">The code on party must be unique.</field>
</record>

View File

@@ -9,7 +9,7 @@ from trytond.i18n import gettext
from trytond.model import (
DeactivableMixin, Index, ModelSQL, ModelView, MultiValueMixin, Unique,
ValueMixin, convert_from, fields, sequence_ordered)
from trytond.model.exceptions import AccessError
from trytond.model.exceptions import AccessError, ValidationError
from trytond.pool import Pool
from trytond.pyson import Bool, Eval
from trytond.tools import is_full_text, lstrip_wildcard
@@ -38,12 +38,34 @@ class PriceValue(
price_value = fields.Float("Price value")
open_price = fields.Float("Open price")
low_price = fields.Float("Low price")
high_price = fields.Float("High price")
def get_price_index(self, name):
if self.price:
return self.price.price_index
return None
high_price = fields.Float("High price")
@classmethod
def validate(cls, price_values):
super().validate(price_values)
cls.check_unique_price_date(price_values)
@classmethod
def check_unique_price_date(cls, price_values):
domains = []
for price_value in price_values:
if not price_value.price or not price_value.price_date:
continue
domain = [
('price', '=', price_value.price.id),
('price_date', '=', price_value.price_date),
]
if price_value.id:
domain.append(('id', '!=', price_value.id))
domains.append(['AND'] + domain)
if domains and cls.search(['OR'] + domains, limit=1):
raise ValidationError(
gettext('price.msg_price_value_unique'))
def get_price_index(self, name):
if self.price:
return self.price.price_index
return None
class PriceValueReport(
ModelSQL, ModelView):

View File

@@ -1,10 +1,10 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from . import (
account,
from trytond.pool import Pool
from . import (
account,
purchase,
sale,
global_reporting,
@@ -32,11 +32,16 @@ from . import (
dimension,
weight_report,
backtoback,
service,
invoice,
)
def register():
service,
invoice,
process_documentation,
routes,
)
__all__ = ['register', 'routes']
def register():
Pool.register(
association_tables.LCDocumentTypeProductProfile,
association_tables.LCLetterDocumentType,
@@ -55,15 +60,19 @@ def register():
lc.LCMT700,
lc.LCMessage,
lc.CreateLCStart,
global_reporting.GRConfiguration,
module='purchase_trade', type_='model')
global_reporting.GRConfiguration,
process_documentation.ProcessDocumentation,
pricing.ImportPricesStart,
pricing.ImportPricesResult,
module='purchase_trade', type_='model')
Pool.register(
incoming.ImportSwift,
incoming.PrepareDocuments,
incoming.AnalyzeConditions,
lc.CreateLCWizard,
module='purchase_trade', type_='wizard'
)
incoming.ImportSwift,
incoming.PrepareDocuments,
incoming.AnalyzeConditions,
lc.CreateLCWizard,
pricing.ImportPrices,
module='purchase_trade', type_='wizard'
)
Pool.register(
credit_risk.Party,
credit_risk.CreditRiskRule,

View File

@@ -0,0 +1,94 @@
# Backlog - Market Price Import
Scope: code changes related only to `BR-PT-004 - Market Price Import`.
## PT-BL-001 - Enforce uniqueness at model level
- Status: done
- Implementation commit: `eaae2e5`
- Priority: high
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: enforce uniqueness of `(price, price_date)` at model level without adding a database constraint yet.
- Work done:
- added model-level validation on `price.price_value`
- added Tryton message `price.msg_price_value_unique`
- added targeted tests for duplicate create and duplicate write validation
- Validation status:
- `git diff --check` passed
- automated Python tests still need to be run in a working Tryton Python environment
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- possibly `modules/price/price_value.py` if the validation belongs on the target model
- Expected tests:
- importing a row for an existing `(price, price_date)` skips or updates according to `Overwrite existing price`
- creating/editing duplicate `price.price_value` records at model level is rejected if validation is implemented on `price.price_value`
## PT-BL-002 - Use locale-aware slash date parsing
- Status: open
- Priority: medium
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: prefer the user's/default locale date format for ambiguous slash dates, then fall back to the current parsing order only if no locale preference is available.
- Current behavior: `DD/MM/YYYY` is tried before `MM/DD/YYYY`.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- slash dates follow the configured/default locale when available
- slash dates keep a deterministic fallback when locale is unavailable
- existing accepted formats still work
## PT-BL-003 - Reject missing price_value
- Status: open
- Priority: high
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: `price_value` is mandatory for imported rows. Missing `price_value` must be reported as a row error.
- Current behavior: empty `price_value` is imported as an empty value.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- missing `price_value` is reported in `errors`
- missing `high_price`, `low_price`, and `open_price` remain allowed
- valid `price_value` still imports and updates correctly
## PT-BL-004 - Detect duplicate rows inside the same Excel file
- Status: open
- Priority: high
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: duplicate rows for the same `(price_index, price_date)` inside the same Excel file must be reported as row errors. The duplicate row must not import or update data.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- duplicate `(price_index, price_date)` rows in the same import produce row errors
- first occurrence behavior remains unchanged
- duplicate rows do not overwrite previous imported or updated values
## PT-BL-005 - Categorize import result errors
- Status: open
- Priority: medium
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: distinguish business validation errors from file, parsing, and technical errors in the import result.
- Current behavior: row errors are grouped under one `Errors` section.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- `modules/purchase_trade/view/import_prices_result_form.xml` only if the result display needs more structure
- Expected tests:
- result message separates business validation errors from parsing/technical errors
- invalid workbook and missing required columns remain blocking `UserError`
- row-level errors still let the import continue
## PT-BL-006 - Fix missing default currency on created price index
- Status: open
- Priority: high
- Source bug: [BUG-PT-001](../bugs.md#bug-pt-001---missing-default-currency-in-price-index-automatic-creation)
- Source rule: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: when the market price import automatically creates a missing `price.price`, the created price index must have default currency `USD`.
- Current behavior: the created price index can miss the default currency even though the import specification expects `USD`.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- automatic price index creation assigns `price_currency` to the `USD` currency record
- if the `USD` currency record cannot be found, behavior remains aligned with the rule for optional default references

View File

@@ -0,0 +1,63 @@
# Bugs - Purchase Trade
This file is the bug register for `purchase_trade`.
## Workflow
1. Register every new bug in this file with a stable bug ID.
2. Create a matching backlog entry for every new bug.
3. Link the bug entry to the backlog entry.
4. When fixed, record the implementation commit in the backlog entry.
5. Keep the bug entry as history and update its status.
## Status Values
- `reported`
- `confirmed`
- `backlog-created`
- `in-progress`
- `to-be-tested`
- `done`
- `rejected`
## Bug Template
```md
### BUG-PT-001 - Short title
- Status: `reported`
- Area:
- Reported date: `YYYY-MM-DD`
- Reported by:
- Related rule:
- Backlog entry:
- Severity:
- Environment:
- Description:
- Steps to reproduce:
- Expected behavior:
- Actual behavior:
- Evidence:
- Notes:
```
## Bugs
### BUG-PT-001 - Missing default currency in Price Index automatic creation
- Status: `backlog-created`
- Area: Market Price Import
- Reported date: `2026-05-08`
- Reported by: user
- Related rule: [BR-PT-004 - Market Price Import](business%20rules/market-price-import.md)
- Backlog entry: [PT-BL-006](backlog/market-price-import-backlog.md#pt-bl-006---fix-missing-default-currency-on-created-price-index)
- Severity:
- Environment:
- Description: during market price import, if a price index should be created, then the default currency defined to USD is missing.
- Steps to reproduce:
- import market prices with `Create price index if missing` enabled
- include a `price_index` that does not already exist
- Expected behavior: the automatically created `price.price` has default currency `USD`.
- Actual behavior: the automatically created `price.price` has no default currency.
- Evidence:
- Notes:

View File

@@ -0,0 +1,50 @@
# BR-PT-003 - Le freight amount des templates facture vient du fee de shipment
## Intent
Afficher dans les documents facture la vraie valeur de fret maritime rattachee au shipment du lot physique.
## Scope
- Domaine: `purchase_trade`
- Flux: templates facture achat et vente
- Priorite: `importante`
## Expected Behavior
Le `FREIGHT VALUE` d'une facture ne doit pas etre pris sur la facture elle-meme.
Il doit etre calcule a partir du `fee.fee` rattache au shipment (`shipment_in`) du lot physique relie a la facture.
Regle de navigation:
- retrouver le lot physique pertinent depuis la facture
- retrouver son shipment
- chercher le `fee.fee` avec:
- `shipment_in = shipment.id`
- `product.name = 'Maritime freight'`
- utiliser `fee.get_amount()` comme montant de fret
La regle s'applique aussi bien aux factures d'achat qu'aux factures de vente.
Cote vente, la remontee doit passer par le lot physique qui fait le lien entre `purchase.line` et `sale.line`.
## Impacted Files
- `modules/purchase_trade/invoice.py`
- `modules/purchase_trade/fee.py`
- `modules/purchase_trade/lot.py`
- Templates facture `.fodt` concernes par `FREIGHT VALUE`
## Tests
Couvrir les changements au plus proche du flux modifie:
- facture achat avec lot physique, shipment et fee `Maritime freight`
- facture vente qui remonte au lot physique puis au shipment
- cas sans fee maritime
- cas sans lot physique pertinent
## Open Questions
- Preciser les templates exacts quand une demande cible un document facture particulier.

View File

@@ -0,0 +1,59 @@
# BR-PT-002 - Le lot physique est le pont metier entre purchase, sale et shipment
## Intent
Disposer d'un chemin unique et stable pour retrouver les informations logistiques et de facturation reliees a un contrat d'achat ou de vente.
## Scope
- Domaine: `purchase_trade`
- Flux: navigation metier entre achat, vente, shipment et facture
- Priorite: `structurante`
## Expected Behavior
Le lot physique (`lot_type = physic`) porte simultanement le lien vers:
- la `purchase.line` via `lot.line`
- la `sale.line` via `lot.sale_line`
- le shipment via `lot.lot_shipment_in` / `lot.lot_shipment_internal` / `lot.lot_shipment_out`
Pour toute logique qui doit naviguer entre achat, vente, shipment et facture, il faut privilegier ce lot physique comme source de verite.
Depuis une facture d'achat:
- remonter a la `purchase.line`
- puis au lot physique de la ligne
- puis au shipment et aux donnees logistiques associees
Depuis une facture de vente:
- remonter a la `sale.line`
- puis au lot physique matchant qui porte aussi la `purchase.line`
- puis au shipment et aux donnees logistiques associees
## Typical Use Cases
- recuperer `bl_date`, `bl_number`, `controller`, `from_location`, `to_location`
- retrouver une facture provisoire liee au lot
- retrouver des fees rattaches au shipment
## Impacted Files
- `modules/purchase_trade/lot.py`
- `modules/purchase_trade/purchase.py`
- `modules/purchase_trade/sale.py`
- `modules/purchase_trade/invoice.py`
- Templates facture relies aux donnees logistiques, si le flux documentaire est concerne.
## Tests
Couvrir les changements au plus proche du flux modifie:
- navigation achat vers lot physique puis shipment
- navigation vente vers lot physique puis shipment
- cas sans lot physique pertinent
## Open Questions
- Documenter au cas par cas les champs exposes au reporting facture quand ils dependent de cette navigation.

View File

@@ -0,0 +1,106 @@
# BR-PT-001 - Ajustement de la quantite theorique apres creation du contrat
## Intent
Conserver la coherence entre la quantite theorique de la ligne d'achat, le lot virtuel associe et les quantites ouvertes stockees dans `lot.qt`.
## Scope
- Domaine: `purchase_trade`
- Flux: modification de `purchase.line.quantity_theorical` apres creation du contrat
- Priorite: `bloquante`
## Inputs
- Une `purchase.line` existe deja.
- Son champ `quantity_theorical` est modifie via `write`.
- Un lot unique de type `virtual` est rattache a la ligne.
## Expected Behavior
Quand `purchase.line.quantity_theorical` est modifiee apres creation du contrat, le systeme doit recalculer le delta entre l'ancienne et la nouvelle valeur.
La regle s'applique au lot unique de type `virtual` rattache a la `purchase.line`.
Si `delta > 0`:
- augmenter la quantite courante du lot `virtual` via `set_current_quantity` pour conserver l'historique `lot.qt.hist`
- augmenter le `lot.qt` ouvert existant
- si aucun `lot.qt` ouvert n'existe, en creer un nouveau avec le delta
Si `delta < 0`:
- diminuer le `lot.qt` ouvert uniquement si la quantite ouverte disponible est suffisante
- diminuer la quantite courante du lot `virtual` du meme delta
- si aucun `lot.qt` ouvert n'existe ou si sa quantite est insuffisante, bloquer avec l'erreur `Please unlink or unmatch lot`
Definition du `lot.qt` ouvert:
- `lot_p = virtual lot`
- `lot_s = None`
- `lot_shipment_in = None`
- `lot_shipment_internal = None`
- `lot_shipment_out = None`
## Edge Cases
- Si aucun lot `virtual` n'est trouve sur la ligne, la regle ne fait rien.
## Examples
### Exemple E1 - Augmentation simple
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 120`
- `lot.qt ouvert = 40`
- Attendu:
- lot `virtual` augmente de `20`
- `lot.qt ouvert` passe de `40` a `60`
### Exemple E2 - Augmentation sans lot.qt ouvert
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 110`
- aucun `lot.qt` ouvert
- Attendu:
- lot `virtual` augmente de `10`
- creation d'un `lot.qt` ouvert a `10`
### Exemple E3 - Diminution possible
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 90`
- `lot.qt ouvert = 25`
- Attendu:
- lot `virtual` diminue de `10`
- `lot.qt ouvert` passe de `25` a `15`
### Exemple E4 - Diminution impossible
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 80`
- `lot.qt ouvert = 5`
- Attendu:
- blocage avec `Please unlink or unmatch lot`
## Impacted Files
- `modules/purchase_trade/purchase.py`
- `modules/purchase_trade/lot.py`
## Tests
Couvrir au minimum:
- augmentation avec `lot.qt` ouvert existant
- augmentation sans `lot.qt` ouvert
- diminution possible
- diminution impossible avec erreur
## Source
- Decision metier documentee dans les commentaires de `purchase_trade.purchase.Line.write`.

View File

@@ -0,0 +1,171 @@
# BR-PT-004 - Market Price Import
## Intent
Import dated market prices from an `.xlsx` Excel file into `price.price_value`, using `price.price` as the market price index.
## Scope
- Wizard: `purchase_trade.import_prices`
- Input model: `purchase_trade.import_prices.start`
- Result model: `purchase_trade.import_prices.result`
- Target models:
- `price.price`
- `price.price_value`
- Menu entry: under `price.menu_price`
## Inputs
The wizard reads only the first worksheet of an `.xlsx` file.
The first row is treated as the header row. Header names are normalized by lowercasing and removing non-alphanumeric characters, so labels such as `price_index`, `price index`, and `Price Index` map to the same field.
Required columns:
- `price_index`
- `price_date`
- `high_price`
- `low_price`
- `open_price`
- `price_value`
The wizard options are:
- `Create price index if missing`
- `Overwrite existing price`
## Date and Numeric Parsing
Accepted `price_date` values:
- Excel serial date numbers
- `YYYY-MM-DD`
- `DD/MM/YYYY`
- `MM/DD/YYYY`
Numeric price fields accept decimal commas or decimal points. Empty numeric cells are imported as empty values. Invalid numeric values are reported as row errors.
## Expected Behavior
For each non-empty data row, starting from Excel row 2:
1. Trim and validate `price_index`.
2. Parse `price_date`.
3. Search `price.price` by exact `price_index`.
4. If the price index is missing:
- create it when `Create price index if missing` is checked
- otherwise skip the row with `price_index missing`
5. Search `price.price_value` by `(price, price_date)`.
6. If an existing price value is found:
- update it when `Overwrite existing price` is checked
- otherwise skip the row with `price_date already exists`
7. If no existing price value is found, create a new `price.price_value`.
## Created Price Index Defaults
When the wizard creates a missing `price.price`, it sets:
- `price_index = imported price_index`
- `price_desc = imported price_index`
- `price_curve_type = future`
It also tries to default these references when matching records exist:
- `price_type`: `price.fixtype` where `name = Market price`
- `price_currency`: `currency.currency` where `code = USD`
- `price_calendar`: `price.calendar` where `name = Argus EU`
- `price_unit`: `product.uom` where `name = Mt`
If the `price_index` contains a `YYYY-MM` style period, the wizard derives a `product.month`:
- pattern accepted in the name: `YYYY-MM`, `YYYY/MM`, `YYYY_MM`, `YYYY.MM`, or `YYYY MM`
- month name format: `MONYY`, for example `JUL26`
- if no matching `product.month` exists, it is created with `is_cotation = True`
## Result Reporting
The result screen always shows counts and detail sections for:
- created price indexes
- imported prices
- updated existing prices
- skipped records
- errors
Row-level errors do not stop the whole import; the wizard records the error and continues with the next row.
## Edge Cases
- Missing `price_index`: skipped.
- Missing `price_date`: skipped.
- Invalid `.xlsx` file: blocking `UserError`.
- Missing required columns: blocking `UserError`.
- Invalid date: row error.
- Invalid numeric value: row error.
- Existing `(price, price_date)` without overwrite option: skipped.
- Existing `(price, price_date)` with overwrite option: updated.
- Empty rows are ignored.
## Impacted Files
Direct `purchase_trade` files:
- `modules/purchase_trade/pricing.py`
- `modules/purchase_trade/pricing.xml`
- `modules/purchase_trade/view/import_prices_start_form.xml`
- `modules/purchase_trade/view/import_prices_result_form.xml`
- `modules/purchase_trade/__init__.py`
- `modules/purchase_trade/tryton.cfg`
- `modules/purchase_trade/tests/test_module.py`
External model dependencies:
- `modules/price/price.py`
- `modules/price/price_value.py`
- `modules/price/view/price_value_form.xml`
## Tests
Existing focused tests cover:
- missing price index skipped when creation is disabled
- missing price index created when creation is enabled
- default fields on newly created price indexes
- period reuse/creation from `price_index`
- existing `price_date` skipped when overwrite is disabled
- existing `price_date` updated when overwrite is enabled
- invalid row values collected as errors
- result screen formatting
Recommended additional tests:
- `.xlsx` header normalization
- missing required columns
- Excel serial date parsing
- empty row ignored
- invalid workbook raises `UserError`
## Open Questions
- Q: Should `(price, price_date)` be enforced unique at model/database level?
- A: Enforce uniqueness at model level for now. Do not add a database constraint yet.
- Q: Should created price index defaults remain hardcoded to `Market price`, `USD`, `Argus EU`, and `Mt`?
- A: Yes. Keep these defaults hardcoded for this import.
- Q: Should ambiguous slash dates prefer `DD/MM/YYYY` over `MM/DD/YYYY`, as currently implemented?
- A: Prefer the user's/default locale date format when possible. Fall back to the current order only if no locale preference is available.
- Comment: current code tries `DD/MM/YYYY` before `MM/DD/YYYY` and does not inspect locale. Implementing this answer requires a code change.
- Q: Should missing `price_value` be allowed, or should it skip/error while high/low/open remain optional?
- A: Missing `price_value` is not allowed. Report the row as an error. `high_price`, `low_price`, and `open_price` remain optional.
- Comment: current code allows empty `price_value` and imports it as an empty value. Implementing this answer requires a code change.
- Q: Should duplicate rows for the same `(price_index, price_date)` inside the same Excel file be treated as an error, skipped after the first row, or resolved by the overwrite option?
- A: Report duplicate rows as row errors and do not import or update the duplicate row.
- Q: When `Create price index if missing` is enabled, should missing default reference records (`Market price`, `USD`, `Argus EU`, `Mt`) block index creation or remain optional as currently implemented?
- A: Remain optional. Create the price index with the reference records that can be found.
- Q: Should the import result distinguish business validation errors from technical parsing errors?
- A: Yes. Distinguish business validation errors from file, parsing, and technical errors in the import result.

View File

@@ -0,0 +1,448 @@
# BR-PT-005 - Shipment Fee Allocation
## Intent
Allocate shipment costs by combining shipment-level fees and contract-level budgeted fees, while avoiding double counting and keeping fee detail rows available for BI analysis.
## Scope
- Domain: `purchase_trade`
- Target: SQL view/query for shipment fee allocation
- Source tables:
- `lot_lot`
- `lot_qt`
- `lot_qt_hist`
- `fee_fee`
- `product_product`
- `party_party`
- `currency_currency`
- `product_uom`
- Fee sources:
- shipment fees linked with `fee_fee.shipment_in`
- purchase contract fees linked with `fee_fee.line`
- sale contract fees linked with `fee_fee.sale_line`
## Business Context
Every purchase contract line has a virtual lot by default. The virtual lot represents the remaining quantity of the contract line.
When physical lots are created, their quantity reduces the virtual lot quantity. Physical lots represent confirmed physical quantities, typically once the Bill of Lading quantity is known.
Before the vessel is sailing or before the exact Bill of Lading quantity is known, costs can already be ordered or scheduled at shipment level. In that case, virtual shipment quantities must still be included as forecast quantities.
## Quantity Source Rules
Shipment quantity is built directly from lot and shipment tables.
Quantity priority:
1. Use physical lots linked to the shipment when they exist.
2. Use virtual lot shipment quantities only when no physical lot exists yet for the same shipment and contract pair.
This prevents double counting:
- physical lots represent confirmed shipment quantities
- virtual lots represent forecast/open quantities until physical lots exist
## Fee Selection Rules
For each `shipment_id + product_id + supplier_id` pair:
1. Use shipment-level fees of type `ordered` first.
2. If shipment quantity remains uncovered, use shipment-level fees of type `scheduled`.
3. If shipment quantity still remains uncovered, use contract-level fees of type `budgeted`.
Contract-level fallback fees:
- purchase contract fees are eligible
- sale contract fees are eligible
- only `type = 'budgeted'` is eligible
- budgeted contract fees are applied to the remaining uncovered shipment quantity
Shipment-level fees:
- only `type IN ('ordered', 'scheduled')` are eligible
- null fee quantity is treated as `0` and does not allocate quantity
- allocated shipment fee quantity is capped to the shipment quantity
If multiple fees exist for the same product/supplier pair at the selected level, keep multiple rows. The view must preserve details for analysis.
## Sign Rules
Use the same sign logic as the existing fee utility views.
Shipment fees:
- `rec` = `+1`
- any other `p_r` value = `-1`
Purchase contract budgeted fees:
- `rec` = `-1`
- any other `p_r` value = `+1`
Sale contract budgeted fees:
- `rec` = `+1`
- any other `p_r` value = `-1`
## Expected Behavior
Example:
- Shipment quantity: `1000 Mt`
- Shipment ordered freight: `800 Mt` at `60 USD/Mt`
- Contract budgeted freight: `50 USD/Mt`
Expected output:
- `800 Mt` at `60 USD/Mt` from shipment ordered fee
- `200 Mt` at `50 USD/Mt` from contract budgeted fee
If a scheduled fee also exists:
- Shipment quantity: `1000 Mt`
- Ordered freight: `800 Mt` at `60 USD/Mt`
- Scheduled freight: `150 Mt` at `55 USD/Mt`
- Contract budgeted freight: `50 USD/Mt`
Expected output:
- `800 Mt` at `60 USD/Mt` from shipment ordered fee
- `150 Mt` at `55 USD/Mt` from shipment scheduled fee
- `50 Mt` at `50 USD/Mt` from contract budgeted fee
## Query Draft
```sql
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 = 'physic'
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 = 'virtual'
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 * FROM physical_shipment_lots
UNION ALL
SELECT * FROM virtual_shipment_lots
),
shipment_context AS (
SELECT
shipment_id,
purchase_line_id,
sale_line_id,
shipment_unit_id,
SUM(COALESCE(shipment_quantity, 0)) AS shipment_quantity
FROM shipment_lots
WHERE shipment_id IS NOT NULL
GROUP BY
shipment_id,
purchase_line_id,
sale_line_id,
shipment_unit_id
),
shipment_base AS (
SELECT
shipment_id,
SUM(shipment_quantity) AS shipment_quantity
FROM shipment_context
GROUP BY shipment_id
),
shipment_fee_candidates AS (
SELECT
f.id AS fee_id,
'Shipment' AS fee_source,
f.type AS fee_type,
CASE
WHEN f.type = 'ordered' THEN 1
WHEN f.type = 'scheduled' THEN 2
END AS priority,
sb.shipment_id,
NULL::integer AS purchase_line_id,
NULL::integer AS 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,
COALESCE(f.quantity, 0) 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' THEN 1
ELSE -1
END AS sign_multiplier
FROM shipment_base sb
JOIN fee_fee f
ON f.shipment_in = sb.shipment_id
WHERE f.type IN ('ordered', 'scheduled')
),
shipment_fee_allocated AS (
SELECT
c.*,
LEAST(
c.requested_quantity,
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
),
0
)
) AS allocated_quantity
FROM shipment_fee_candidates c
),
shipment_fee_coverage AS (
SELECT
shipment_id,
product_id,
supplier_id,
MAX(shipment_quantity) AS shipment_quantity,
SUM(allocated_quantity) AS allocated_quantity
FROM shipment_fee_allocated
GROUP BY shipment_id, product_id, supplier_id
),
contract_budgeted_fee_candidates AS (
SELECT
f.id AS fee_id,
'Purchase Contract' AS fee_source,
f.type AS fee_type,
3 AS priority,
sc.shipment_id,
sc.purchase_line_id,
NULL::integer AS 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' THEN -1
ELSE 1
END AS sign_multiplier
FROM shipment_context sc
JOIN fee_fee f
ON f.line = sc.purchase_line_id
WHERE f.type = 'budgeted'
AND sc.purchase_line_id IS NOT NULL
UNION ALL
SELECT
f.id AS fee_id,
'Sale Contract' AS fee_source,
f.type AS fee_type,
3 AS priority,
sc.shipment_id,
NULL::integer AS 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' THEN 1
ELSE -1
END AS sign_multiplier
FROM shipment_context sc
JOIN fee_fee f
ON f.sale_line = sc.sale_line_id
WHERE f.type = 'budgeted'
AND sc.sale_line_id IS NOT NULL
),
contract_budgeted_allocated AS (
SELECT
c.*,
GREATEST(
c.shipment_quantity - COALESCE(fc.allocated_quantity, 0),
0
) 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
),
final_fees AS (
SELECT
fee_id,
fee_source,
fee_type,
priority,
shipment_id,
purchase_line_id,
sale_line_id,
product_id,
supplier_id,
packaging,
pay_or_rec,
state,
weight_type,
allocated_quantity AS fee_quantity,
fee_price,
currency_id,
unit_id,
sign_multiplier
FROM shipment_fee_allocated
WHERE allocated_quantity > 0
UNION ALL
SELECT
fee_id,
fee_source,
fee_type,
priority,
shipment_id,
purchase_line_id,
sale_line_id,
product_id,
supplier_id,
packaging,
pay_or_rec,
state,
weight_type,
allocated_quantity AS fee_quantity,
fee_price,
currency_id,
unit_id,
sign_multiplier
FROM contract_budgeted_allocated
WHERE allocated_quantity > 0
)
SELECT
f.shipment_id AS "intShipmentId",
f.fee_id AS "intFeeId",
f.fee_source AS "Fee Source",
f.fee_type AS "Fee Type",
f.priority AS "Priority",
f.purchase_line_id AS "intPurchaseLineId",
f.sale_line_id AS "intSaleLineId",
f.product_id AS "intProductId",
p.code AS "Fee",
f.supplier_id AS "intSupplierId",
sup.name AS "Supplier",
f.packaging AS "Packaging",
f.pay_or_rec AS "Pay or Rec",
f.state AS "State",
CASE
WHEN upper(f.weight_type::text) = 'BRUT' THEN 'Gross'
ELSE 'Net'
END AS "Weighing Type",
f.fee_quantity AS "Quantity",
f.fee_price * f.sign_multiplier AS "Price",
cur.name AS "Currency",
COALESCE(uom.name, 'Mt') AS "Unit",
f.fee_quantity * f.fee_price * f.sign_multiplier AS "Amount",
CASE
WHEN upper(p.code::text) LIKE '%FREIGHT%' THEN 'Freight'
WHEN upper(p.code::text) LIKE '%PROFIT SHARING%' THEN 'Profit Sharing'
ELSE 'Other Costs'
END AS "Cost Group"
FROM final_fees f
JOIN product_product p
ON p.id = f.product_id
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
ORDER BY
f.shipment_id,
f.product_id,
f.supplier_id,
f.priority,
f.fee_id;
```
## Impacted Files
Expected SQL/view impact:
- BI SQL view or migration file that will materialize the shipment fee allocation query
- Existing related views for comparison:
- `vw_utility_contract_fees`
- `vw_utility_shipment_fees`
- `vw_bi_itsa_fct_contract_fees`
- `vw_bi_itsa_fct_shipment_fees`
No Python code impact is expected unless the view is generated by module migration code.
## Tests
Recommended tests:
- shipment with no ordered/scheduled fee uses full budgeted contract fee quantity
- shipment with ordered fee covering full quantity does not use budgeted fallback
- shipment with ordered fee partially covering quantity uses budgeted fallback for remaining quantity
- shipment with ordered and scheduled fees uses ordered first, scheduled second, budgeted third
- shipment fee quantity above shipment quantity is capped
- null shipment fee quantity does not allocate quantity
- multiple fees at the same priority are kept as multiple detail rows
- physical lot quantity replaces virtual forecast quantity for the same shipment/contract pair
- virtual lot quantity is used when no physical lot exists yet
## Open Questions
- Q: Should this query become a permanent PostgreSQL view?
- Q: What should the final view name be?
- Q: Should the output include both allocated quantity and original fee quantity for auditability?

View File

@@ -1,10 +1,10 @@
# Business Rules - Purchase Trade
Statut: `draft`
Version: `v0.2`
Derniere mise a jour: `2026-03-27`
Owner metier: `a completer`
Owner technique: `a completer`
Version: `v0.3`
Derniere mise a jour: `2026-05-07`
Owner metier: `Open Squared`
Owner technique: `Open Squared`
## 1) Scope
@@ -13,145 +13,46 @@ Owner technique: `a completer`
- Modules impactes:
- `purchase_trade`
- `lot`
- `price`
## 2) Glossaire
- `Purchase Line`: ligne d'achat.
- `quantity_theorical`: quantite theorique contractuelle de la ligne.
- `Virtual Lot`: lot unique de type `virtual` rattache a une `purchase.line`.
- `lot.qt`: table des quantites ouvertes, matchées ou shippées par lot.
- `Physical Lot`: lot physique de type `physic` utilise comme pont entre achat, vente et shipment.
- `lot.qt`: table des quantites ouvertes, matchees ou shippees par lot.
- `lot.qt ouvert`: enregistrement `lot.qt` avec `lot_p = virtual lot`, `lot_s = None` et sans shipment.
- `price.price`: index de prix marche.
- `price.price_value`: valeur datee d'un index de prix marche.
## 3) Regles metier
## 3) Catalogue des regles
### BR-PT-001 - Ajustement de la quantite theorique apres creation du contrat
| ID | Titre | Domaine | Detail |
| --- | --- | --- | --- |
| BR-PT-001 | Ajustement de la quantite theorique apres creation du contrat | Lot / Purchase | [business rules/lot-quantity.md](business%20rules/lot-quantity.md) |
| BR-PT-002 | Le lot physique est le pont metier entre purchase, sale et shipment | Lot / Navigation | [business rules/lot-navigation.md](business%20rules/lot-navigation.md) |
| BR-PT-003 | Le freight amount des templates facture vient du fee de shipment | Invoice / Freight | [business rules/invoice-freight.md](business%20rules/invoice-freight.md) |
| BR-PT-004 | Market Price Import | Pricing | [business rules/market-price-import.md](business%20rules/market-price-import.md) |
| BR-PT-005 | Shipment Fee Allocation | Shipment / Fees | [business rules/shipment_fee_allocation.md](business%20rules/shipment_fee_allocation.md) |
- Intent: conserver la coherence entre la quantite theorique de la ligne d'achat, le lot virtuel associe et les quantites ouvertes stockees dans `lot.qt`.
- Description:
- Quand `purchase.line.quantity_theorical` est modifiee apres creation du contrat, le systeme doit recalculer le delta entre l'ancienne et la nouvelle valeur.
- La regle s'applique au lot unique de type `virtual` rattache a la `purchase.line`.
- Conditions d'entree:
- Une `purchase.line` existe deja.
- Son champ `quantity_theorical` est modifie via `write`.
- Un lot `virtual` est rattache a la ligne.
- Resultat attendu:
- Si `delta > 0`:
- augmenter la quantite courante du lot `virtual` via `set_current_quantity` pour conserver l'historique `lot.qt.hist`
- augmenter le `lot.qt` ouvert existant
- si aucun `lot.qt` ouvert n'existe, en creer un nouveau avec le delta
- Si `delta < 0`:
- diminuer le `lot.qt` ouvert uniquement si la quantite ouverte disponible est suffisante
- diminuer la quantite courante du lot `virtual` du meme delta
- si aucun `lot.qt` ouvert n'existe ou si sa quantite est insuffisante, bloquer avec l'erreur `Please unlink or unmatch lot`
- Definition du `lot.qt` ouvert:
- `lot_p = virtual lot`
- `lot_s = None`
- `lot_shipment_in = None`
- `lot_shipment_internal = None`
- `lot_shipment_out = None`
- Exceptions:
- si aucun lot `virtual` n'est trouve sur la ligne, la regle ne fait rien
- Priorite:
- `bloquante`
- Source:
- `Decision metier documentee dans les commentaires de purchase_trade.purchase.Line.write`
## 4) Convention pour les nouvelles regles
### BR-PT-002 - Le lot physique est le pont metier entre purchase, sale et shipment
Voir aussi [documentation-management.md](documentation-management.md) pour le workflow complet de gestion documentaire.
- Intent: disposer d'un chemin unique et stable pour retrouver les informations logistiques et de facturation reliees a un contrat d'achat ou de vente.
- Description:
- Le lot physique (`lot_type = physic`) porte simultanement le lien vers:
- la `purchase.line` via `lot.line`
- la `sale.line` via `lot.sale_line`
- le shipment via `lot.lot_shipment_in` / `lot.lot_shipment_internal` / `lot.lot_shipment_out`
- Pour toute logique qui doit naviguer entre achat, vente, shipment et facture, il faut privilegier ce lot physique comme source de verite.
- Resultat attendu:
- depuis une facture d'achat:
- remonter a la `purchase.line`
- puis au lot physique de la ligne
- puis au shipment et aux donnees logistiques associees
- depuis une facture de vente:
- remonter a la `sale.line`
- puis au lot physique matchant qui porte aussi la `purchase.line`
- puis au shipment et aux donnees logistiques associees
- Cas d'usage typiques:
- recuperer `bl_date`, `bl_number`, `controller`, `from_location`, `to_location`
- retrouver une facture provisoire liee au lot
- retrouver des fees rattaches au shipment
- Priorite:
- `structurante`
Ajouter une ligne au catalogue puis creer une fiche detaillee dans `docs/business rules/`.
### BR-PT-003 - Le freight amount des templates facture vient du fee de shipment
Structure recommandee:
- Intent: afficher dans les documents facture la vraie valeur de fret maritime rattachee au shipment du lot physique.
- Description:
- Le `FREIGHT VALUE` d'une facture ne doit pas etre pris sur la facture elle-meme.
- Il doit etre calcule a partir du `fee.fee` rattache au shipment (`shipment_in`) du lot physique relie a la facture.
- Regle de navigation:
- retrouver le lot physique pertinent depuis la facture
- retrouver son shipment
- chercher le `fee.fee` avec:
- `shipment_in = shipment.id`
- `product.name = 'Maritime freight'`
- utiliser `fee.get_amount()` comme montant de fret
- Portee:
- s'applique aussi bien aux factures d'achat qu'aux factures de vente
- cote vente, la remontee doit passer par le lot physique qui fait le lien entre `purchase.line` et `sale.line`
- Priorite:
- `importante`
- Intent
- Scope
- Inputs
- Expected Behavior
- Edge Cases
- Impacted Files
- Tests
- Open Questions
## 4) Exemples concrets
Les regles courtes peuvent rester dans ce catalogue temporairement, mais toute regle avec impact code, tests, edge cases ou flux transverse doit avoir son fichier dedie.
### Exemple E1 - Augmentation simple
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 120`
- `lot.qt ouvert = 40`
- Attendu:
- lot `virtual` augmente de `20`
- `lot.qt ouvert` passe de `40` a `60`
### Exemple E2 - Augmentation sans lot.qt ouvert
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 110`
- aucun `lot.qt` ouvert
- Attendu:
- lot `virtual` augmente de `10`
- creation d'un `lot.qt` ouvert a `10`
### Exemple E3 - Diminution possible
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 90`
- `lot.qt ouvert = 25`
- Attendu:
- lot `virtual` diminue de `10`
- `lot.qt ouvert` passe de `25` a `15`
### Exemple E4 - Diminution impossible
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 80`
- `lot.qt ouvert = 5`
- Attendu:
- blocage avec `Please unlink or unmatch lot`
## 5) Impact code attendu
- Fichiers Python concernes:
- `modules/purchase_trade/purchase.py`
- `modules/purchase_trade/lot.py`
## 6) Strategie de tests
Pour cette regle, couvrir au minimum:
- augmentation avec `lot.qt` ouvert existant
- augmentation sans `lot.qt` ouvert
- diminution possible
- diminution impossible avec erreur
Dans les sections `Open Questions`, prefixer chaque question avec `Q:` et chaque reponse documentee avec `A:`.

View File

@@ -0,0 +1,113 @@
# Documentation Management - Purchase Trade
This guide records the documentation workflow to apply for future `purchase_trade` developments.
## Rule Documentation Structure
- Keep `business-rules.md` as the entry point and rule catalog.
- Store detailed business rules in `docs/business rules/`, one file per substantial rule.
- Use stable business rule IDs such as `BR-PT-004`.
- Add every new detailed rule to the catalog in `business-rules.md`.
- Keep rule files focused on business behavior, expected outcomes, edge cases, impacted files, tests, and open questions.
## Rule File Template
Use this structure for detailed rule files when applicable:
- Intent
- Scope
- Inputs
- Date and Numeric Parsing, when relevant
- Expected Behavior
- Defaults or Derived Values, when relevant
- Result Reporting, when relevant
- Edge Cases
- Impacted Files
- Tests
- Open Questions
## Open Questions
- In `Open Questions`, prefix every question with `Q:`.
- Prefix every documented answer with `A:`.
- When an answer differs from current code behavior, add a short `Comment:` explaining that a code change is required.
- Once all questions have answers, keep the section as decision history unless the rule becomes noisy.
Example:
```md
- Q: Should missing `price_value` be allowed?
- A: Missing `price_value` is not allowed. Report the row as an error.
- Comment: current code allows empty `price_value`. Implementing this answer requires a code change.
```
## Backlog From Documentation
- When answered questions imply code changes, create a dedicated backlog file for the feature or rule.
- Store all backlog files in `docs/backlog/`.
- Name backlog files by feature, for example `docs/backlog/market-price-import-backlog.md`.
- Do not use a generic module backlog when the changes belong to one feature.
- After coding a backlog item, update that item with the work done and mark its `Status` as `to be tested` until the targeted validation has run successfully in the proper environment.
- After committing implemented backlog work, record the implementation commit hash in the relevant backlog item for future reference.
- Each backlog item should include:
- stable ID
- status
- implementation commit, once committed
- priority
- source rule file
- requirement
- work done, once implemented
- validation status, once attempted
- current behavior, when useful
- expected code impact
- expected tests
Backlog status values:
- `open`: identified, not started yet
- `in-progress`: currently being worked on
- `blocked`: cannot progress until a dependency or question is resolved
- `implemented`: code or documentation change made, not yet validated
- `to-be-tested`: implemented and waiting for targeted validation in the right environment
- `tested`: targeted validation passed
- `done`: tested, committed, and no further action expected
- `rejected`: intentionally not implemented
Default workflow:
```text
open -> in-progress -> to-be-tested -> tested -> done
```
Use `implemented` only when the change is made but no validation attempt has happened yet. Use `to-be-tested` when validation must happen in another environment or could not be completed locally.
## Bug Management
- Register every new bug in `docs/bugs.md`.
- Use stable bug IDs such as `BUG-PT-001`.
- Every new bug must have a matching backlog entry.
- Link the bug entry to its backlog entry.
- If the bug belongs to an existing feature backlog, add it there.
- If the bug does not clearly belong to an existing feature backlog, create a dedicated backlog file under `docs/backlog/`.
- When the bug is fixed, record the implementation commit in the backlog entry and update the bug status.
## Review Workflow
For future development work:
1. Read the relevant rule file before changing code.
2. Review the current code and tests related to the rule.
3. Update the rule documentation if code review reveals missing behavior, edge cases, defaults, or test gaps.
4. Record unresolved decisions as `Q:` entries.
5. Record accepted decisions as `A:` entries.
6. Convert accepted decisions that require code changes into backlog items.
7. Implement code only after the requested documentation/specification step is complete, or after explicit approval when the user asks to wait.
## Scope Discipline
- Keep documentation changes close to the affected module and feature.
- Do not mix unrelated features in one rule file or one backlog file.
- Link or list impacted files so future agents can work in a narrow code scope.
- Keep tests listed near the rule they validate.

View File

@@ -0,0 +1,322 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Structure documentaire Markdown - purchase_trade</title>
<style>
@page {
size: A4;
margin: 18mm 16mm;
}
body {
color: #172033;
font-family: "Segoe UI", Arial, sans-serif;
font-size: 11px;
line-height: 1.42;
margin: 0;
}
h1 {
color: #0f2742;
font-size: 25px;
line-height: 1.1;
margin: 0 0 6px;
}
h2 {
border-bottom: 2px solid #d8e2ef;
color: #18466f;
font-size: 16px;
margin: 22px 0 8px;
padding-bottom: 4px;
}
h3 {
color: #1f5c8f;
font-size: 13px;
margin: 13px 0 4px;
}
p {
margin: 5px 0;
}
code {
background: #eef3f8;
border-radius: 3px;
color: #0f3a5f;
font-family: Consolas, monospace;
font-size: 10px;
padding: 1px 3px;
}
table {
border-collapse: collapse;
margin: 8px 0 12px;
width: 100%;
}
th, td {
border: 1px solid #d4dde8;
padding: 6px 7px;
text-align: left;
vertical-align: top;
}
th {
background: #eaf2fb;
color: #14395a;
font-weight: 650;
}
ul {
margin: 5px 0 10px 18px;
padding: 0;
}
li {
margin: 3px 0;
}
.subtitle {
color: #5b6b7f;
font-size: 12px;
margin-bottom: 16px;
}
.callout {
background: #f4f8fc;
border-left: 4px solid #2d78b7;
margin: 9px 0 12px;
padding: 8px 10px;
}
.warning {
background: #fff7e8;
border-left-color: #d98718;
}
.ok {
background: #edf8f0;
border-left-color: #329c50;
}
.tree {
background: #f7f9fb;
border: 1px solid #dce5ee;
border-radius: 4px;
font-family: Consolas, monospace;
font-size: 10px;
padding: 9px 11px;
white-space: pre-wrap;
}
.page-break {
break-before: page;
}
.small {
color: #5b6b7f;
font-size: 10px;
}
</style>
</head>
<body>
<h1>Structure documentaire Markdown - purchase_trade</h1>
<p class="subtitle">Document recapitulatif de la structure mise en place pour les regles metier, bugs, backlogs et gestion documentaire.</p>
<div class="callout">
<strong>Objectif general.</strong>
La documentation est organisee pour separer les regles metier, les bugs, les backlogs d'implementation et les conventions de gestion. L'objectif est de garder un contexte lisible pour les humains et chargeable a la demande pour les agents.
</div>
<h2>1. Arborescence Markdown</h2>
<div class="tree">modules/purchase_trade/docs/
business-rules.md
documentation-management.md
bugs.md
template-rules.md
business rules/
lot-quantity.md
lot-navigation.md
invoice-freight.md
market-price-import.md
backlog/
market-price-import-backlog.md</div>
<h2>2. Role et description des fichiers</h2>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Role</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>business-rules.md</code></td>
<td>Catalogue des regles metier</td>
<td>Point d'entree des regles <code>purchase_trade</code>. Contient le scope, le glossaire, le catalogue des IDs <code>BR-PT-xxx</code> et les liens vers les fiches detaillees.</td>
</tr>
<tr>
<td><code>documentation-management.md</code></td>
<td>Regles de gestion documentaire</td>
<td>Decrit le workflow: creation de regles, questions ouvertes <code>Q:</code>/<code>A:</code>, backlogs, bugs, statuts, commits de reference et discipline de scope.</td>
</tr>
<tr>
<td><code>bugs.md</code></td>
<td>Registre des bugs</td>
<td>Lieu d'enregistrement initial des bugs avec ID stable <code>BUG-PT-xxx</code>. Chaque bug doit etre relie a une entree backlog.</td>
</tr>
<tr>
<td><code>template-rules.md</code></td>
<td>Regles specifiques aux templates</td>
<td>Guide de correction et d'analyse des templates Relatorio/FODT, notamment les ponts Python, les placeholders XML et le cache des reports facture.</td>
</tr>
<tr>
<td><code>business rules/lot-quantity.md</code></td>
<td>Regle detaillee BR-PT-001</td>
<td>Specifie l'ajustement de <code>quantity_theorical</code>, la synchronisation du lot virtuel et des quantites ouvertes <code>lot.qt</code>.</td>
</tr>
<tr>
<td><code>business rules/lot-navigation.md</code></td>
<td>Regle detaillee BR-PT-002</td>
<td>Documente le lot physique comme pont stable entre purchase, sale, shipment et facture.</td>
</tr>
<tr>
<td><code>business rules/invoice-freight.md</code></td>
<td>Regle detaillee BR-PT-003</td>
<td>Definit que le <code>FREIGHT VALUE</code> des factures vient du fee de shipment <code>Maritime freight</code>, via le lot physique.</td>
</tr>
<tr>
<td><code>business rules/market-price-import.md</code></td>
<td>Regle detaillee BR-PT-004</td>
<td>Specifie l'import de prix marche depuis Excel: colonnes attendues, parsing, creation d'index, resultats, edge cases et questions tranchees.</td>
</tr>
<tr>
<td><code>backlog/market-price-import-backlog.md</code></td>
<td>Backlog feature Market Price Import</td>
<td>Liste les travaux issus de BR-PT-004 et des bugs associes, avec statut, priorite, source, impact code, tests attendus et commit d'implementation si applicable.</td>
</tr>
</tbody>
</table>
<h2>3. Workflow recommande</h2>
<h3>Nouvelle regle metier</h3>
<ul>
<li>Ajouter une ligne dans <code>business-rules.md</code>.</li>
<li>Creer une fiche dediee dans <code>business rules/</code> si la regle a des impacts code, tests, edge cases ou questions.</li>
<li>Utiliser des questions <code>Q:</code> et reponses <code>A:</code>; ajouter <code>Comment:</code> si la decision implique un changement code.</li>
</ul>
<h3>Nouveau bug</h3>
<ul>
<li>Creer une entree dans <code>bugs.md</code> avec un ID stable <code>BUG-PT-xxx</code>.</li>
<li>Creer une entree backlog correspondante, soit dans un backlog existant, soit dans un nouveau fichier sous <code>backlog/</code>.</li>
<li>Lier le bug au backlog et le backlog au bug.</li>
</ul>
<h3>Implementation</h3>
<ul>
<li>Passer le backlog de <code>open</code> a <code>in-progress</code>, puis <code>to-be-tested</code> apres implementation.</li>
<li>Documenter le travail realise et le statut de validation.</li>
<li>Apres commit, enregistrer le hash du commit dans l'entree backlog.</li>
</ul>
<h2>4. Avantages</h2>
<ul>
<li><strong>Tra&ccedil;abilite.</strong> Les decisions metier, bugs, backlogs, tests et commits sont relies.</li>
<li><strong>Scope clair.</strong> Un agent peut charger seulement le fichier pertinent au lieu de tout lire.</li>
<li><strong>Meilleure maintenance.</strong> Les regles lourdes vivent dans des fiches dediees; le catalogue reste court.</li>
<li><strong>Priorisation explicite.</strong> Le backlog garde statut, priorite, tests attendus et validation.</li>
<li><strong>Historique de decision.</strong> Les sections <code>Open Questions</code> gardent les arbitrages <code>Q:</code>/<code>A:</code>.</li>
<li><strong>Support bug propre.</strong> Chaque bug a un ID stable et une entree backlog obligatoire.</li>
</ul>
<h2>5. Inconvenients / points de vigilance</h2>
<ul>
<li><strong>Discipline necessaire.</strong> Il faut maintenir les liens et les statuts, sinon la structure perd de sa valeur.</li>
<li><strong>Risque de fragmentation.</strong> Trop de petits fichiers peuvent ralentir la comprehension si le catalogue n'est pas tenu a jour.</li>
<li><strong>Noms avec espaces.</strong> Le dossier <code>business rules/</code> est lisible, mais les liens Markdown doivent utiliser <code>business%20rules</code> dans les URLs.</li>
<li><strong>Backlog a synchroniser.</strong> Une correction code doit etre reportee dans le backlog, notamment le statut et le commit.</li>
<li><strong>Duplication possible.</strong> Certaines informations peuvent apparaitre dans regle, bug et backlog; il faut garder chaque fichier dans son role.</li>
</ul>
<div class="page-break"></div>
<h2>6. Estimation de consommation tokens</h2>
<p>Estimation basee sur la regle pratique de l'image fournie: <strong>1 KB &asymp; 200 tokens</strong>.</p>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Taille approx.</th>
<th>Tokens approx.</th>
<th>Usage conseille</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>backlog/market-price-import-backlog.md</code></td>
<td>4.70 KB</td>
<td>~939</td>
<td>Charger pour prioriser ou coder un item Market Price Import.</td>
</tr>
<tr>
<td><code>bugs.md</code></td>
<td>1.67 KB</td>
<td>~333</td>
<td>Charger au moment de declarer ou suivre un bug.</td>
</tr>
<tr>
<td><code>business rules/invoice-freight.md</code></td>
<td>1.49 KB</td>
<td>~298</td>
<td>Charger pour les sujets freight/facture.</td>
</tr>
<tr>
<td><code>business rules/lot-navigation.md</code></td>
<td>1.84 KB</td>
<td>~369</td>
<td>Charger pour les chemins achat/vente/shipment.</td>
</tr>
<tr>
<td><code>business rules/lot-quantity.md</code></td>
<td>2.87 KB</td>
<td>~574</td>
<td>Charger pour les changements de quantite theorique.</td>
</tr>
<tr>
<td><code>business rules/market-price-import.md</code></td>
<td>6.21 KB</td>
<td>~1 242</td>
<td>Charger pour comprendre la specification complete de l'import.</td>
</tr>
<tr>
<td><code>business-rules.md</code></td>
<td>2.21 KB</td>
<td>~441</td>
<td>Charger comme index de depart.</td>
</tr>
<tr>
<td><code>documentation-management.md</code></td>
<td>4.47 KB</td>
<td>~893</td>
<td>Charger quand on gere docs, bugs, statuts ou backlog.</td>
</tr>
<tr>
<td><code>template-rules.md</code></td>
<td>6.14 KB</td>
<td>~1 227</td>
<td>Charger uniquement pour les sujets templates/reporting.</td>
</tr>
<tr>
<th>Total des 9 fichiers</th>
<th>31.58 KB</th>
<th>~6 316</th>
<th>Faible a modere si charge ensemble; meilleur en chargement a la demande.</th>
</tr>
</tbody>
</table>
<h3>Evaluation</h3>
<div class="callout ok">
<strong>Consommation estimee: faible a moderee.</strong>
A environ 6 300 tokens pour tout le dossier Markdown <code>purchase_trade/docs</code>, la structure reste tres raisonnable par rapport a un budget conseille de 60k-80k tokens. Le risque principal n'est pas un fichier unique, mais l'accumulation avec l'historique de conversation, les diffs, les logs et les sorties de tests.
</div>
<div class="callout warning">
<strong>Bonne pratique.</strong>
Charger <code>business-rules.md</code> comme index, puis seulement la fiche detaillee, le bug ou le backlog utile. Eviter de charger tous les fichiers docs plus de gros logs ou des diffs complets dans la meme interaction.
</div>
<h2>7. Recommandation finale</h2>
<p>La structure est pertinente et peu couteuse en tokens si elle est utilisee comme une documentation modulaire. Elle donne une bonne base pour travailler avec des agents: contexte court au depart, approfondissement a la demande, liens explicites entre bug, regle, backlog, tests et commit.</p>
<p class="small">Document genere le 2026-05-08 pour le module <code>purchase_trade</code>.</p>
</body>
</html>

View File

@@ -1,9 +1,10 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, Id
from trytond.model import (ModelSQL, ModelView)
from trytond.model import fields
from trytond.exceptions import UserError
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, Id
from trytond.model import (ModelSQL, ModelView)
from trytond.tools import is_full_text, lstrip_wildcard
from trytond.transaction import Transaction, inactive_records
from decimal import getcontext, Decimal, ROUND_HALF_UP
@@ -11,12 +12,17 @@ from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr
from sql.conditionals import Case
from sql import Column, Literal
from sql.functions import CurrentTimestamp, DateTrunc
from trytond.wizard import Button, StateTransition, StateView, Wizard
from itertools import chain, groupby
from operator import itemgetter
import datetime
import logging
from trytond.modules.purchase_trade.purchase import (TRIGGERS)
from trytond.wizard import Button, StateTransition, StateView, Wizard
from itertools import chain, groupby
from operator import itemgetter
import calendar
import datetime
import logging
import re
import zipfile
from io import BytesIO
from xml.etree import ElementTree
from trytond.modules.purchase_trade.purchase import (TRIGGERS)
logger = logging.getLogger(__name__)
@@ -50,15 +56,506 @@ DAYS = [
('sunday', 'Sunday'),
]
class Estimated(ModelSQL, ModelView):
"Estimated date"
__name__ = 'pricing.estimated'
class Estimated(ModelSQL, ModelView):
"Estimated date"
__name__ = 'pricing.estimated'
trigger = fields.Selection(TRIGGERS,"Trigger")
estimated_date = fields.Date("Estimated date")
fin_int_delta = fields.Integer("Financing interests delta")
class MtmScenario(ModelSQL, ModelView):
estimated_date = fields.Date("Estimated date")
fin_int_delta = fields.Integer("Financing interests delta")
class ImportPricesStart(ModelView):
"Import Prices"
__name__ = 'purchase_trade.import_prices.start'
file_structure = fields.Selection([
('historical', "Historical Prices"),
('forward', "Forward Prices"),
], "Excel file structure", required=True)
file_ = fields.Binary('Excel file', required=True, filename='filename')
filename = fields.Char('Filename')
create_missing_price_index = fields.Boolean(
"Create price index if missing")
overwrite_existing_price = fields.Boolean("Overwrite existing price")
@staticmethod
def default_file_structure():
return 'historical'
class ImportPricesResult(ModelView):
"Import Prices Result"
__name__ = 'purchase_trade.import_prices.result'
message = fields.Text("Results", readonly=True)
class ImportPrices(Wizard):
"Import Prices"
__name__ = 'purchase_trade.import_prices'
start = StateView(
'purchase_trade.import_prices.start',
'purchase_trade.import_prices_start_view_form',
[
Button('Cancel', 'end', 'tryton-cancel'),
Button('Import', 'import_', 'tryton-ok', default=True),
])
import_ = StateTransition()
result = StateView(
'purchase_trade.import_prices.result',
'purchase_trade.import_prices_result_view_form',
[
Button('OK', 'end', 'tryton-ok', default=True),
])
REQUIRED_COLUMNS = {
'priceindex': 'price_index',
'pricedate': 'price_date',
'highprice': 'high_price',
'lowprice': 'low_price',
'openprice': 'open_price',
'pricevalue': 'price_value',
}
def transition_import_(self):
rows = self._read_xlsx(
self.start.file_, file_structure=self.start.file_structure)
stats = self._import_rows(
rows,
create_missing_price_index=(
self.start.create_missing_price_index),
overwrite_existing_price=self.start.overwrite_existing_price)
self._result_message = self._format_result(stats)
return 'result'
def default_result(self, fields):
return {
'message': getattr(
self, '_result_message',
'No import result was produced.'),
}
@classmethod
def _import_rows(
cls, rows, create_missing_price_index=False,
overwrite_existing_price=False):
Price = Pool().get('price.price')
PriceValue = Pool().get('price.price_value')
stats = {
'created_indexes': [],
'imported': [],
'updated': [],
'skipped': [],
'errors': [],
}
for index, row in enumerate(rows, start=2):
row_number = row.get('_row_number', index)
price_index = (row.get('price_index') or '').strip()
try:
price_date = cls._as_date(row.get('price_date'))
if not price_index:
stats['skipped'].append(
cls._result_line(
row_number, '', None, 'missing price_index'))
continue
if not price_date:
stats['skipped'].append(
cls._result_line(
row_number, price_index, None,
'missing price_date'))
continue
prices = Price.search(
[('price_index', '=', price_index)], limit=1)
if prices:
price = prices[0]
elif create_missing_price_index:
price, = Price.create([
cls._price_index_values(price_index)])
stats['created_indexes'].append(
cls._result_line(
row_number, price_index, None,
'price index created'))
else:
stats['skipped'].append(
cls._result_line(
row_number, price_index, None,
'price_index missing'))
continue
values = cls._price_value_values(price, row, price_date)
existing = PriceValue.search([
('price', '=', price.id),
('price_date', '=', price_date),
], limit=1)
if existing:
if overwrite_existing_price:
PriceValue.write(existing, values)
stats['updated'].append(
cls._result_line(
row_number, price_index, price_date,
cls._price_summary(values)))
else:
stats['skipped'].append(cls._result_line(
row_number, price_index, price_date,
'price_date already exists'))
continue
PriceValue.create([values])
stats['imported'].append(
cls._result_line(
row_number, price_index, price_date,
cls._price_summary(values)))
except Exception as exception:
stats['errors'].append(
cls._result_line(
row_number, price_index, row.get('price_date'),
str(exception)))
continue
return stats
@classmethod
def _price_index_values(cls, price_index):
values = {
'price_index': price_index,
'price_desc': price_index,
'price_curve_type': 'future',
}
references = [
('price_type', 'price.fixtype', [('name', '=', 'Market price')]),
('price_currency', 'currency.currency', [('name', '=', 'USD')]),
('price_calendar', 'price.calendar', [('name', '=', 'Argus EU')]),
('price_unit', 'product.uom', [('name', '=', 'Mt')]),
]
for field, model_name, domain in references:
record = cls._first_record(model_name, domain)
if record:
values[field] = record.id
period = cls._period_from_price_index(price_index)
if period:
values['price_period'] = period.id
return values
@classmethod
def _period_from_price_index(cls, price_index):
match = re.search(r'(?<!\d)(20\d{2})[-_/\. ](0[1-9]|1[0-2])(?!\d)',
price_index)
if not match:
return None
year = int(match.group(1))
month = int(match.group(2))
month_name = cls._period_month_name(year, month)
periods = cls._period_model().search(
[('month_name', '=', month_name)], limit=1)
if periods:
return periods[0]
beg_date = datetime.date(year, month, 1)
end_date = datetime.date(
year, month, calendar.monthrange(year, month)[1])
period, = cls._period_model().create([{
'month_name': month_name,
'description': month_name,
'beg_date': beg_date,
'end_date': end_date,
'is_cotation': True,
}])
return period
@staticmethod
def _period_month_name(year, month):
return '%s%s' % (
calendar.month_abbr[month].upper(), str(year)[-2:])
@staticmethod
def _period_model():
return Pool().get('product.month')
@staticmethod
def _first_record(model_name, domain):
records = Pool().get(model_name).search(domain, limit=1)
return records[0] if records else None
@classmethod
def _price_value_values(cls, price, row, price_date):
return {
'price': price.id,
'price_date': price_date,
'high_price': cls._as_float(row.get('high_price')),
'low_price': cls._as_float(row.get('low_price')),
'open_price': cls._as_float(row.get('open_price')),
'price_value': cls._as_float(row.get('price_value')),
}
@staticmethod
def _result_line(row_number, price_index, price_date, detail):
return {
'row': row_number,
'price_index': price_index,
'price_date': price_date,
'detail': detail,
}
@classmethod
def _format_result(cls, stats):
lines = [
'Import completed.',
f"Created price indexes: {len(stats['created_indexes'])}",
f"Imported prices: {len(stats['imported'])}",
f"Updated prices: {len(stats['updated'])}",
f"Skipped rows: {len(stats['skipped'])}",
f"Errors: {len(stats['errors'])}",
]
sections = [
('Created price indexes', stats['created_indexes']),
('Successfully imported prices', stats['imported']),
('Updated existing prices', stats['updated']),
('Skipped records', stats['skipped']),
('Errors', stats['errors']),
]
for title, items in sections:
lines.append('')
lines.append('%s:' % title)
if not items:
lines.append('- None')
continue
for item in items:
lines.append('- %s' % cls._format_result_line(item))
return '\n'.join(lines)
@staticmethod
def _format_result_line(item):
label = item['price_index'] or '<empty price_index>'
price_date = item['price_date']
if isinstance(price_date, datetime.date):
price_date = price_date.isoformat()
if price_date:
label = '%s / %s' % (label, price_date)
return 'Row %(row)s - ' % item + '%s: %s' % (label, item['detail'])
@staticmethod
def _price_summary(values):
parts = []
for name in ('price_value', 'open_price', 'low_price', 'high_price'):
value = values.get(name)
if value is not None:
parts.append('%s=%s' % (name, value))
return ', '.join(parts) or 'price imported'
@classmethod
def _read_xlsx(cls, data, file_structure='historical'):
try:
with zipfile.ZipFile(BytesIO(data)) as workbook:
shared_strings = cls._read_shared_strings(workbook)
sheet_name = cls._first_sheet_name(workbook)
sheet = ElementTree.fromstring(workbook.read(sheet_name))
except (KeyError, zipfile.BadZipFile, ElementTree.ParseError):
raise UserError("The selected file is not a valid Excel .xlsx file.")
ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
rows = sheet.findall('.//s:sheetData/s:row', ns)
if not rows:
return []
if file_structure == 'forward':
return cls._read_forward_price_rows(rows, shared_strings, ns)
return cls._read_historical_price_rows(rows, shared_strings, ns)
@classmethod
def _read_historical_price_rows(cls, rows, shared_strings, ns):
headers = {}
for cell in rows[0].findall('s:c', ns):
index = cls._cell_column_index(cell.get('r'))
header = cls._cell_value(cell, shared_strings)
normalized = cls._normalize_header(header)
if normalized in cls.REQUIRED_COLUMNS:
headers[index] = cls.REQUIRED_COLUMNS[normalized]
missing = set(cls.REQUIRED_COLUMNS.values()) - set(headers.values())
if missing:
raise UserError(
"Missing columns in Excel file: %s"
% ', '.join(sorted(missing)))
result = []
for sheet_row in rows[1:]:
values = {}
for cell in sheet_row.findall('s:c', ns):
index = cls._cell_column_index(cell.get('r'))
field = headers.get(index)
if field:
values[field] = cls._cell_value(cell, shared_strings)
if any(v not in (None, '') for v in values.values()):
result.append(values)
return result
@classmethod
def _read_forward_price_rows(cls, rows, shared_strings, ns):
headers = {}
month_terms = {}
for cell in rows[0].findall('s:c', ns):
index = cls._cell_column_index(cell.get('r'))
header = cls._cell_value(cell, shared_strings)
normalized = cls._normalize_header(header)
if index == 1 and normalized == 'priceindex':
headers[index] = 'price_index'
elif index == 2 and normalized == 'pricedate':
headers[index] = 'price_date'
elif index and index >= 3 and header not in (None, ''):
month_terms[index] = cls._as_month_term(header)
missing = []
if headers.get(1) != 'price_index':
missing.append('price_index')
if headers.get(2) != 'price_date':
missing.append('price_date')
if missing:
raise UserError(
"Missing columns in Excel file: %s" % ', '.join(missing))
if not month_terms:
raise UserError(
"Missing month term columns in Excel file.")
result = []
for excel_row_number, sheet_row in enumerate(rows[1:], start=2):
row_values = {}
for cell in sheet_row.findall('s:c', ns):
index = cls._cell_column_index(cell.get('r'))
row_values[index] = cls._cell_value(cell, shared_strings)
price_index = (row_values.get(1) or '').strip()
price_date = row_values.get(2)
if not price_index and price_date in (None, ''):
continue
for index, month_term in sorted(month_terms.items()):
price_value = row_values.get(index)
if price_value in (None, ''):
continue
result.append({
'_row_number': excel_row_number,
'price_index': (
'%s %s' % (price_index, month_term)
if price_index else ''),
'price_date': price_date,
'price_value': price_value,
})
return result
@staticmethod
def _read_shared_strings(workbook):
try:
content = workbook.read('xl/sharedStrings.xml')
except KeyError:
return []
root = ElementTree.fromstring(content)
ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
strings = []
for item in root.findall('s:si', ns):
strings.append(''.join(
text.text or '' for text in item.findall('.//s:t', ns)))
return strings
@staticmethod
def _first_sheet_name(workbook):
workbook_xml = ElementTree.fromstring(workbook.read('xl/workbook.xml'))
rels_xml = ElementTree.fromstring(
workbook.read('xl/_rels/workbook.xml.rels'))
wb_ns = {
's': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'r': (
'http://schemas.openxmlformats.org/officeDocument/2006/'
'relationships'),
}
rel_ns = {
'r': (
'http://schemas.openxmlformats.org/package/2006/'
'relationships'),
}
sheet = workbook_xml.find('.//s:sheet', wb_ns)
rel_id = sheet.get(
'{http://schemas.openxmlformats.org/officeDocument/2006/'
'relationships}id')
for rel in rels_xml.findall('r:Relationship', rel_ns):
if rel.get('Id') == rel_id:
target = rel.get('Target')
if not target.startswith('/'):
target = 'xl/' + target
return target.lstrip('/')
raise KeyError('No worksheet found')
@staticmethod
def _cell_column_index(reference):
match = re.match(r'([A-Z]+)', reference or '')
if not match:
return None
index = 0
for char in match.group(1):
index = index * 26 + ord(char) - ord('A') + 1
return index
@classmethod
def _cell_value(cls, cell, shared_strings):
ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
if cell.get('t') == 'inlineStr':
text = cell.find('.//s:t', ns)
return text.text if text is not None else ''
value = cell.find('s:v', ns)
if value is None:
return ''
if cell.get('t') == 's':
return shared_strings[int(value.text)]
return value.text
@staticmethod
def _normalize_header(value):
return re.sub(r'[^a-z0-9]', '', (value or '').strip().lower())
@staticmethod
def _as_float(value):
if value in (None, ''):
return None
return float(str(value).replace(',', '.'))
@staticmethod
def _as_date(value):
if isinstance(value, datetime.date):
return value
if value in (None, ''):
return None
text = str(value).strip()
if re.match(r'^\d+(\.\d+)?$', text):
return (
datetime.date(1899, 12, 30)
+ datetime.timedelta(days=int(float(text))))
for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y'):
try:
return datetime.datetime.strptime(text, fmt).date()
except ValueError:
pass
raise UserError("Invalid price_date: %s" % text)
@classmethod
def _as_month_term(cls, value):
if isinstance(value, datetime.date):
return value.strftime('%Y-%m')
text = str(value).strip()
match = re.match(r'^(20\d{2})[-_/\. ](0[1-9]|1[0-2])$', text)
if match:
return '%s-%s' % (match.group(1), match.group(2))
if re.match(r'^\d+(\.\d+)?$', text):
return cls._as_date(text).strftime('%Y-%m')
raise UserError("Invalid month term: %s" % text)
class MtmScenario(ModelSQL, ModelView):
"MtM Scenario"
__name__ = 'mtm.scenario'

View File

@@ -20,12 +20,29 @@ this repository contains the full copyright notices and license terms. -->
<field name="priority" eval="20"/>
<field name="name">summary_tree_sequence</field>
</record>
<record model="ir.ui.view" id="estimated_view_tree">
<field name="model">pricing.estimated</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">estimated_tree</field>
</record>
<record model="ir.ui.view" id="estimated_view_tree">
<field name="model">pricing.estimated</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">estimated_tree</field>
</record>
<record model="ir.ui.view" id="import_prices_start_view_form">
<field name="model">purchase_trade.import_prices.start</field>
<field name="type">form</field>
<field name="name">import_prices_start_form</field>
</record>
<record model="ir.ui.view" id="import_prices_result_view_form">
<field name="model">purchase_trade.import_prices.result</field>
<field name="type">form</field>
<field name="name">import_prices_result_form</field>
</record>
<record model="ir.action.wizard" id="act_import_prices">
<field name="name">Import Prices</field>
<field name="wiz_name">purchase_trade.import_prices</field>
<field name="window" eval="True"/>
</record>
<record model="ir.ui.view" id="component_view_tree">
<field name="model">pricing.component</field>
@@ -181,12 +198,17 @@ this repository contains the full copyright notices and license terms. -->
sequence="99"
id="menu_mtm"
icon="tradon-mtm" />
<menuitem
name="Strategy"
action="act_strategy_form"
parent="menu_mtm"
sequence="10"
id="menu_strategy" />
</data>
</tryton>
<menuitem
name="Strategy"
action="act_strategy_form"
parent="menu_mtm"
sequence="10"
id="menu_strategy" />
<menuitem
parent="price.menu_price"
sequence="85"
action="act_import_prices"
id="menu_import_prices"/>
</data>
</tryton>

View File

@@ -0,0 +1,314 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import base64
import html
import mimetypes
import os
import posixpath
import re
from pathlib import Path
from urllib.parse import unquote
from trytond.model import ModelSingleton, ModelSQL, ModelView, fields
from trytond.transaction import Transaction
DOCUMENTATION_ROOT = Path(__file__).with_name('process_documentation')
MARKDOWN_LINK = re.compile(r'(!?)\[([^\]]*)\]\(([^)]+)\)')
HTML_IMAGE_SOURCE = re.compile(
r'(<img\b[^>]*\bsrc=)(["\'])([^"\']+)(\2)', re.IGNORECASE)
class ProcessDocumentation(ModelSingleton, ModelSQL, ModelView):
"Process Documentation"
__name__ = 'purchase_trade.process.documentation'
content = fields.Function(fields.Text('Content'), 'get_content')
@staticmethod
def default_content():
return _html_content()
def get_content(self, name):
return _html_content()
def _html_content():
html_file = Transaction().context.get('process_documentation_html')
if html_file:
return _html_file_content(html_file)
document = HTML_TEMPLATE % {
'content': _documentation_content(),
}
return IFRAME_TEMPLATE % {
'document': html.escape(document, quote=True),
}
def _html_file_content(filename):
path = (DOCUMENTATION_ROOT / Path(*filename.split('/'))).resolve()
root = DOCUMENTATION_ROOT.resolve()
common_path = os.path.commonpath([str(root), str(path)])
if path != root and common_path == str(root):
if path.exists() and path.is_file() and path.suffix.lower() == '.html':
document = path.read_text(encoding='utf-8')
document = _inline_html_assets(document, _relative_name(path))
return IFRAME_TEMPLATE % {
'document': html.escape(document, quote=True),
}
return _html_error('Documentation page not found: %s' % filename)
def _inline_html_assets(document, current_file):
def replace(match):
prefix, quote, source, suffix = match.groups()
resolved = _resolve_link(current_file, source)
if resolved.startswith(('http://', 'https://', 'data:', 'mailto:')):
return match.group(0)
data_uri = _asset_data_uri(resolved)
if not data_uri:
return match.group(0)
return '%s%s%s%s' % (prefix, quote, html.escape(data_uri, quote=True),
suffix)
return HTML_IMAGE_SOURCE.sub(replace, document)
def _html_error(message):
document = HTML_TEMPLATE % {
'content': '<p>%s</p>' % html.escape(message),
}
return IFRAME_TEMPLATE % {
'document': html.escape(document, quote=True),
}
def _documentation_content():
sections = []
index = DOCUMENTATION_ROOT / 'README.md'
if index.exists():
sections.append(_render_page(index))
for path in sorted(DOCUMENTATION_ROOT.rglob('*.md')):
if path == index:
continue
sections.append(_render_page(path))
return '\n'.join(sections)
def _render_page(path):
relative_name = _relative_name(path)
markdown = path.read_text(encoding='utf-8')
return '<section id="%s">%s</section>' % (
_anchor(relative_name), _markdown_to_html(markdown, relative_name))
def _relative_name(path):
return path.relative_to(DOCUMENTATION_ROOT.resolve()).as_posix()
def _anchor(filename):
return 'doc-' + re.sub(r'[^a-zA-Z0-9_-]+', '-', filename)
def _resolve_link(current_file, target):
if target.startswith(('#', 'http://', 'https://', 'mailto:')):
return target
target = unquote(target).replace('\\', '/')
current_dir = posixpath.dirname(current_file)
return posixpath.normpath(posixpath.join(current_dir, target)).lstrip('/')
def _asset_data_uri(filename):
path = (DOCUMENTATION_ROOT / Path(*filename.split('/'))).resolve()
root = DOCUMENTATION_ROOT.resolve()
common_path = os.path.commonpath([str(root), str(path)])
if path != root and common_path == str(root):
if path.exists() and path.is_file():
mimetype = mimetypes.guess_type(str(path))[0]
mimetype = mimetype or 'application/octet-stream'
data = base64.b64encode(path.read_bytes()).decode('ascii')
return 'data:%s;base64,%s' % (mimetype, data)
def _inline(markdown, current_file):
parts = []
last = 0
for match in MARKDOWN_LINK.finditer(markdown):
parts.append(html.escape(markdown[last:match.start()]))
is_image, label, target = match.groups()
resolved = _resolve_link(current_file, target)
label = html.escape(label)
if is_image:
source = _asset_data_uri(resolved)
if source:
parts.append('<img src="%s" alt="%s"/>' % (
html.escape(source, quote=True), label))
else:
parts.append(label)
elif resolved.endswith('.md'):
parts.append(_internal_link(_anchor(resolved), label))
elif resolved.startswith('#'):
parts.append(_internal_link(resolved.lstrip('#'), label))
elif resolved.startswith(('http://', 'https://', 'mailto:')):
parts.append('<a href="%s">%s</a>' % (
html.escape(resolved, quote=True), label))
else:
parts.append(html.escape(match.group(0)))
last = match.end()
parts.append(html.escape(markdown[last:]))
return ''.join(parts)
def _markdown_to_html(markdown, current_file):
body = []
table = []
in_list = False
def close_list():
nonlocal in_list
if in_list:
body.append('</ul>')
in_list = False
def flush_table():
nonlocal table
if table:
close_list()
body.append(_render_table(table, current_file))
table = []
for line in markdown.splitlines():
stripped = line.strip()
if stripped.startswith('|') and stripped.endswith('|'):
table.append(stripped)
continue
flush_table()
if not stripped:
close_list()
continue
if stripped.startswith('#'):
close_list()
level = len(stripped) - len(stripped.lstrip('#'))
level = min(level, 6)
title = stripped[level:].strip()
body.append('<h%d>%s</h%d>' % (
level, _inline(title, current_file), level))
elif stripped.startswith('- '):
if not in_list:
body.append('<ul>')
in_list = True
body.append('<li>%s</li>' % _inline(
stripped[2:].strip(), current_file))
else:
close_list()
body.append('<p>%s</p>' % _inline(stripped, current_file))
flush_table()
close_list()
if current_file != 'README.md':
body.insert(
0, '<p>%s</p>' % _internal_link('doc-README-md', 'Back to index'))
return '\n'.join(body)
def _internal_link(anchor, label):
anchor = html.escape(anchor, quote=True)
return '<a href="#%s" data-doc-target="%s">%s</a>' % (
anchor, anchor, label)
def _render_table(lines, current_file):
rows = [
[cell.strip() for cell in line.strip('|').split('|')]
for line in lines
]
header = rows[0] if rows else []
data = rows[1:]
if data and all(set(cell) <= {'-', ':'} for cell in data[0]):
data = data[1:]
output = ['<table>', '<thead><tr>']
for cell in header:
output.append('<th>%s</th>' % _inline(cell, current_file))
output.append('</tr></thead><tbody>')
for row in data:
output.append('<tr>')
for cell in row:
output.append('<td>%s</td>' % _inline(cell, current_file))
output.append('</tr>')
output.append('</tbody></table>')
return ''.join(output)
IFRAME_TEMPLATE = '''<iframe srcdoc="%(document)s"
style="border: 0; height: calc(100vh - 160px); min-height: 760px; width: 100%%;"
title="Tradon Processes"></iframe>
'''
HTML_TEMPLATE = '''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<style>
body {
color: #1f2933;
font-family: Arial, sans-serif;
line-height: 1.5;
margin: 0;
padding: 24px;
}
a {
color: #0b63ce;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
table {
border-collapse: collapse;
margin: 16px 0;
width: 100%%;
}
th,
td {
border: 1px solid #d7dde5;
padding: 8px 10px;
text-align: left;
vertical-align: top;
}
th {
background: #f3f6f9;
}
img {
height: auto;
max-width: 100%%;
}
</style>
</head>
<body>
<main>%(content)s</main>
<script>
document.addEventListener('click', function(event) {
var link = event.target.closest('a[data-doc-target]');
if (!link) {
return;
}
var target = document.getElementById(link.dataset.docTarget);
if (!target) {
return;
}
event.preventDefault();
target.scrollIntoView({block: 'start'});
});
</script>
</body>
</html>
'''

View File

@@ -0,0 +1,64 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="process_documentation_view_form">
<field name="model">purchase_trade.process.documentation</field>
<field name="type">form</field>
<field name="name">process_documentation_form</field>
</record>
<record model="ir.action.act_window" id="act_tradon_processes">
<field name="name">Tradon Processes</field>
<field name="res_model">purchase_trade.process.documentation</field>
</record>
<record model="ir.action.act_window.view"
id="act_tradon_processes_view_form">
<field name="sequence" eval="10"/>
<field name="view" ref="process_documentation_view_form"/>
<field name="act_window" ref="act_tradon_processes"/>
</record>
<record model="ir.action.act_window" id="act_itsa_operations_workflow">
<field name="name">Interacid Practice Book</field>
<field name="res_model">purchase_trade.process.documentation</field>
<field name="context" eval="{
'process_documentation_html': 'ITSA/Interacid_Tradon_Practice_Book.html',
}" pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_itsa_operations_workflow_view_form">
<field name="sequence" eval="10"/>
<field name="view" ref="process_documentation_view_form"/>
<field name="act_window" ref="act_itsa_operations_workflow"/>
</record>
<menuitem
name="Help &amp; Processes"
sequence="95"
icon="tryton-public"
id="menu_help_processes"/>
<menuitem
name="Tradon Processes"
parent="menu_help_processes"
action="act_tradon_processes"
sequence="10"
id="menu_tradon_processes"/>
<menuitem
name="Interacid Practice Book"
parent="menu_help_processes"
action="act_itsa_operations_workflow"
sequence="20"
id="menu_itsa_operations_workflow"/>
<record model="ir.model.access" id="access_process_documentation">
<field name="model">purchase_trade.process.documentation</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,212 @@
# Practice Book Generation Guideline
A reusable specification for producing **ERP "Practice Book" user manuals** for commodity
trading clients, derived from two reference documents:
- *Trading, Middle Office and Derivatives Practice Book* (front-office)
- *Shipping Practice Book v2* (operations / back-office)
Use this guideline as the blueprint when generating an equivalent document for **another
customer, another commodity set, or a different workflow** (and, where relevant, a
different ERP such as Tryton instead of iRely).
---
## 1. Purpose of these documents
A Practice Book is a **"to-be" operational manual**. It is *not* generic vendor software
documentation. Its job is to tell a specific client's staff **how their business is to be
run inside the configured ERP**, step by step, screen by screen.
Each book has a clear scope along the trade lifecycle:
| Book | Scope | Primary audience |
|------|-------|-----------------|
| **Trade / Middle Office / Derivatives** | Front office: contract capture, pricing, FX fixation, cost budgeting, hedging, broker reconciliation, market exposure, allocation, sales | Traders, Trader Assistants, Market Risk / Derivatives Desk, Finance |
| **Shipping / Logistics** | Back office: logistics flows, shipping instructions, load shipment, inventory receipt, vouchers, invoicing, exception handling | Operations, Logistics, Trader Assistants, Finance |
**Defining characteristics to reproduce:**
- Written in the client's own vocabulary and entity names (companies, commodities, ports, banks).
- Describes the agreed *target process*, including explicit decisions made during implementation ("we have decided to…", "for simplicity we keep…").
- Heavily screenshot-driven: every action is illustrated with an annotated capture of the real configured system.
- Honest about gaps: carries open questions, "to be confirmed", and vendor action tags inline.
- Organised lifecycle-first (follow the goods/contract from creation to settlement), not feature-first.
---
## 2. Document structure (section template)
Reproduce this skeleton. Sections marked **[shared]** are written once and reused
verbatim across every book for the same client.
```
Title page — Client / workflow name
Contents — Auto-generated TOC with page numbers
1. Fundamentals & glossary [shared]
- Company / Location and Line of Business
- Contract pricing types (e.g. Priced vs Basis)
- Contract sequences (multi-line contracts) + any limits agreed
- Contracts budget & costs (route / cost-matrix concept)
- Contract items (product catalogue philosophy)
- Price fixations & hedging (one-liner overview)
- Hedging & broker reconciliations (overview)
- Allocations & reservations (overview)
- Logistics flows (Inbound / Outbound / Drop Ship / Transfer)
- Key process names (Shipping Instruction, Load Shipment, Inventory Receipt …)
- System-specific term mapping (e.g. "Voucher vs Invoice")
2. Main subject chapters (lifecycle-ordered)
- Each major process = a chapter
- Each chapter = intro paragraph + numbered/illustrated steps
- Field-reference tables for every data-entry screen
3. Worked scenarios
- One "base scenario" (happy path), fully illustrated end to end
- Labelled variants of the base scenario
e.g. "Variant-2 (Location: X, Commodity: Y)"
- Miscellaneous / exception flows (rejections, claims, transhipment …)
- Optionally: "Scenarios that will not happen / for later phases"
4. Appendix
- Overview diagram(s) of end-to-end flow
- Cross-reference table (e.g. helpdesk / ticket references)
- Footnotes collected from the body
```
> The two reference books deliberately **share section 1 verbatim**. Keep this discipline:
> write the fundamentals once per client and paste identically into each book so staff get
> the same grounding regardless of which manual they open.
---
## 3. Content building blocks (the repeatable units)
### 3.1 Field-reference table
Used for every data-entry screen. The leading number ties each row to a numbered red
callout on the adjacent screenshot.
```
| # | Attribute | Description |
| - | --------- | ----------- |
| 1 | <Field> | What it is, how it is used, who fills it, defaulting rules, worked example |
```
Rules:
- Descriptions are **operational**, not just definitional — say *who* enters it, *when*, *why*, and any calculation. Long cells with embedded examples are normal and expected.
- Use a `???` / `@vendor: to complete` placeholder for fields not yet finalised rather than omitting the row.
- Keep field labels in **bold** when referenced in prose.
### 3.2 Step sequence
Process actions as a bulleted list, in execution order, each meaningful step followed by a
screenshot:
```
- Trader clicks **Insert** … [screenshot]
- A new window opens; enter the line details [field-reference table]
- Click **Save** — system allocates the number (e.g. PC- / SC- prefix)
```
### 3.3 Annotated screenshot
The dominant visual. Conventions to reproduce:
- Real captures of the *configured* system populated with the client's data.
- **Numbered red circular callouts** placed on the fields, matching `#` in the field table.
- Placed immediately after the prose/table that describes them, never front-loaded.
- For exception flows, before/after captures.
### 3.4 Notes, examples, open items
- **Note / Important Note:** bolded inline callouts for rules, warnings, responsibilities.
- *Italic worked examples* with concrete numbers (e.g. freight 4500 USD ÷ 19.2 MT = 234.38 USD/MT).
- `[@vendor: …]` inline tags for open questions, bugs, or future enhancements — kept visible in the draft.
- Footnotes (`[^n]`) for edge cases that would interrupt the main flow.
### 3.5 Worked-calculation block
For any computed value (freight, finance, insurance, FX), show the **formula then a fully
numeric example**:
```
Finance cost = Principal × Rate × Days / 365
= 87 848.77 × 2.0% × 60 / 365 = 288.82 USD
```
---
## 4. Formatting & style conventions
| Element | Convention |
|---------|-----------|
| Field / column names | **Bold** |
| Buttons / actions | **Bold** (e.g. click **Save**, **Insert**, **Allocate**) |
| Worked examples | *Italic*, with real numbers |
| System term being defined | **Bold** on first use |
| Open questions / vendor actions | `[@vendor: …]` inline, left in the text |
| Edge cases | Footnotes |
| Process / status names | Capitalised exactly as in the system (In-Transit, Spot, In-Store) |
| Headings | H1 = chapter, H2 = process, H3/H4 = sub-process or scenario variant |
| Voice | Future/target tense ("we will create…", "the Trader will enter…") |
Keep the **client's real master data** in every example: company/location names,
commodities, ports, incoterms, banks, grades, packaging (e.g. 69 kg bags). This grounding
is what makes the book usable and is the single most important thing to localise.
---
## 5. How to adapt for a new customer / commodity / workflow
> **INSTRUCTION — ask before generating.** Before producing a new Practice Book, do **not**
> assume answers from the reference documents. First **ask the customer the questions
> below and wait for their answers.** Only once the answers are provided should the output
> document be generated. Pose the questions grouped as listed; where a question has a small
> fixed set of choices, offer them as selectable options, otherwise ask for free text.
**Questions to ask the customer:**
1. **Glossary entities.** What are the company/location name(s), the commodity set being
traded (e.g. coffee, cocoa, cotton, sugar, grains, iron ore, steel, copper, aluminium,
zinc/lead), and the line-of-business dimension? Are there any agreed structural limits
(e.g. maximum number of contract sequences/lines)?
2. **Pricing types.** Which pricing types does the client use — *Priced*, *Basis
(differential)*, *formula-priced*, or a combination? Which underlying markets and terms
apply (e.g. ICE, LME, SHFE, GAFTA/FOSFA references)?
3. **Cost matrix & route logic.** What are the real incoterms, typical loading and
destination places, and the cost types to budget (e.g. freight, insurance, finance,
fumigation, inland, demurrage)? Which costs are auto-calculated vs. manually entered?
4. **Logistics flows in scope.** Which flows apply — Inbound (origin → warehouse),
Outbound (warehouse → buyer), Drop Ship (direct), Transfer (location to location)? Are
the goods bagged or bulk (affecting packaging, weights, draft survey, moisture/outturn),
and is tolerance/franchise handling required for the commodity family?
5. **Hedging & FX scope.** What should the book cover — *physical only (no hedging/FX)*,
*futures hedging only*, *futures + FX hedging*, or *FX only*? Front-office and physical
mechanics must be kept in clearly separate chapters.
6. **Worked scenarios.** Which dimensions matter for this client's scenario variants
(e.g. location, commodity, contract type, packaging, exception type)? Which exception
flows must be covered (rejections, claims, transhipment …) and which are out of
scope / later phase?
7. **Screenshots.** Will annotated screenshots be supplied from the client's configured
environment, or should the document leave numbered placeholders for them? (Never reuse
another client's captures.)
8. **Target ERP.** Which ERP is the target — Tryton, iRely, or another system? If it
differs from the reference (iRely), provide the equivalent term/model mapping (e.g.
Tryton models, wizards, states) so iRely-specific terms (Voucher, Sequence, Blotter)
can be replaced. The pedagogical structure — fundamentals, field tables, illustrated
steps, worked scenarios — is ERP-agnostic and must be preserved regardless.
---
## 6. Quality bar (acceptance criteria)
A generated Practice Book is "done" when:
- A new staff member could execute each process end-to-end using only the book.
- Every data-entry screen has a matching numbered field table + annotated screenshot.
- Every computed figure shows its formula and a numeric worked example.
- All examples use the client's real master data.
- Open items are visibly tagged, not silently dropped.
- The fundamentals chapter is identical across the client's set of books.
- Scope boundaries (what is in / out / later phase) are stated explicitly.

View File

@@ -0,0 +1,29 @@
# Customer master data management
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Credit management & credit limit assessment
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Sales enquiry & quotation management
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Sales order creation & confirmation
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Order scheduling & promising
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Contract management
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Pricing & discount management
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Inventory reservation & availability check
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Pick, pack & ship (outbound logistics)
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Delivery note & shipping documentation
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Customer invoicing & billing
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Revenue recognition
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Accounts receivable management
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Collections management & dunning
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Cash application & payment matching
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Customer dispute & deduction management
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Customer returns & credit notes
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Bad debt provisioning & write-off
Parent process: Order to Cash (O2C) / Sales to Collection
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Vendor master data management
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Spend category management
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Purchase requisition
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Sourcing & RFQ/RFP management
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Supplier evaluation & selection
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Purchase order creation & approval
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Contract & framework agreement management
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Goods receipt & inspection (inbound logistics)
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Service entry sheet / service confirmation
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Invoice receipt & verification (3-way match)
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Accounts payable management
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Payment run & disbursement
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Early payment discounts & supply chain finance
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Vendor dispute management
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Goods returns & debit notes
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Vendor performance management
Parent process: Purchase to Pay (P2P) / Procure to Disbursement
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Demand planning & forecasting
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Master production scheduling (MPS)
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Material requirements planning (MRP)
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Capacity planning & work centre management
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Bill of materials (BOM) management
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Routing & work instruction management
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Production order creation & release
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Shop floor execution & work-in-progress tracking
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Material issue to production
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Quality control & inspection during production
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Production confirmation & yield reporting
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Co-product & by-product handling
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Production order settlement & costing
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Scrap & rework management
Parent process: Plan to Produce / Make to Stock / Make to Order
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Warehouse structure & location management
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Goods receipt processing
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Put-away & storage management
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Inventory transfers & stock movements
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Pick, pack & goods issue
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Batch & serial number management
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Lot traceability & expiry management
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Physical inventory counting & reconciliation
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Inventory adjustments & write-offs
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Stock valuation & costing
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Bonded warehouse & customs management
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Cold chain & temperature management
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# 3PL / outsourced warehouse management
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Warehouse KPI & performance management
Parent process: Inventory & Warehouse Management
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Chart of accounts management
Parent process: Record to Report (R2R) / Accounting to Financial Statements
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

View File

@@ -0,0 +1,29 @@
# Journal entry & period-end accruals
Parent process: Record to Report (R2R) / Accounting to Financial Statements
## Purpose
Describe the objective of this subprocess and its expected control outcome in Tradon.
## Tradon / Tryton execution
- Screens and menu entries: to be completed.
- Models, fields and reports: to be completed.
- Operating steps: to be completed.
## COSO control considerations
- Control environment: to be completed.
- Risk assessment: to be completed.
- Control activities: to be completed.
- Information and communication: to be completed.
- Monitoring: to be completed.
## Evidence and records
- Documents, approvals, audit trail or reports: to be completed.
## Open points
- To be completed.

Some files were not shown because too many files have changed in this diff Show More