From 206adbc8bbe70f09e9eb7bd910867eb16b5f2ad5 Mon Sep 17 00:00:00 2001 From: Victor Fraile Garcia Date: Sat, 30 May 2026 20:05:42 +0200 Subject: [PATCH] Add Margin Intelligence and Cost Readiness Cockpit --- .../services/order-report-api/app/main.py | 225 ++++ .../app/templates/cost_readiness.html | 250 ++++ .../app/templates/orders_index.html | 120 ++ ...080_margin_intelligence_cost_readiness.sql | 1186 +++++++++++++++++ 4 files changed, 1781 insertions(+) create mode 100644 ucepsa/edge-oee-demo/services/order-report-api/app/templates/cost_readiness.html create mode 100644 ucepsa/edge-oee-demo/services/order-report-api/app/templates/orders_index.html create mode 100644 ucepsa/edge-oee-demo/sql/reports/080_margin_intelligence_cost_readiness.sql diff --git a/ucepsa/edge-oee-demo/services/order-report-api/app/main.py b/ucepsa/edge-oee-demo/services/order-report-api/app/main.py index 78b5365..5918a0d 100644 --- a/ucepsa/edge-oee-demo/services/order-report-api/app/main.py +++ b/ucepsa/edge-oee-demo/services/order-report-api/app/main.py @@ -119,11 +119,40 @@ def build_report_context(order_ref: str) -> dict[str, Any]: (order_ref,), ) + margin = fetch_one( + """ + SELECT * + FROM mv_reports_ucepsa_demo.order_margin_report_v1 + WHERE odoo_order_ref = %s + """, + (order_ref,), + ) + + raw_materials = fetch_all( + """ + SELECT + raw_product_default_code, + raw_product_name, + planned_qty, + consumed_qty, + raw_uom, + material_cost_eur_per_kg, + material_cost_estimated_eur_rounded, + material_cost_status + FROM mv_reports_ucepsa_demo.order_raw_material_costs_v1 + WHERE odoo_order_ref = %s + ORDER BY raw_product_default_code, raw_product_name + """, + (order_ref,), + ) + return { "title": REPORT_TITLE, "report": report, "stops": stops, "timeline": timeline, + "margin": margin, + "raw_materials": raw_materials, } @@ -172,3 +201,199 @@ def report_pdf(order_ref: str = Query(..., description="Odoo order reference, e. "Content-Disposition": f'inline; filename="{filename}"' }, ) + + +@app.get("/api/cost-readiness") +def api_cost_readiness(limit: int = Query(100, ge=1, le=500)): + summary = fetch_one( + """ + SELECT * + FROM mv_reports_ucepsa_demo.cost_readiness_summary_v1 + """, + (), + ) + + orders = fetch_all( + """ + SELECT + odoo_order_ref, + odoo_state, + sale_order_ref, + customer_name, + product_default_code, + product_name, + machine_id, + revenue_net_eur, + raw_total_consumed_qty, + raw_materials_summary, + cost_readiness_score, + cost_readiness_status, + cost_readiness_description, + technical_quality_level, + final_report_status, + margin_status + FROM mv_reports_ucepsa_demo.cost_readiness_orders_v1 + ORDER BY cost_readiness_score ASC, odoo_order_ref DESC + LIMIT %s + """, + (limit,), + ) + + raw_materials = fetch_all( + """ + SELECT + raw_product_default_code, + raw_product_name, + raw_uom, + affected_order_count, + affected_orders, + affected_sale_orders, + affected_customers, + total_consumed_qty, + current_material_cost_eur_per_kg, + raw_material_readiness_status, + recommended_action + FROM mv_reports_ucepsa_demo.cost_readiness_raw_materials_v1 + ORDER BY affected_order_count DESC, raw_product_default_code + """, + (), + ) + + return jsonable_encoder( + { + "summary": summary or {}, + "orders": orders, + "raw_materials": raw_materials, + } + ) + + +@app.get("/cost-readiness.html", response_class=HTMLResponse) +def cost_readiness_html(request: Request, limit: int = Query(100, ge=1, le=500)): + summary = fetch_one( + """ + SELECT * + FROM mv_reports_ucepsa_demo.cost_readiness_summary_v1 + """, + (), + ) + + orders = fetch_all( + """ + SELECT + odoo_order_ref, + odoo_state, + sale_order_ref, + customer_name, + product_default_code, + product_name, + machine_id, + revenue_net_eur, + raw_total_consumed_qty, + raw_materials_summary, + cost_readiness_score, + cost_readiness_status, + cost_readiness_description, + technical_quality_level, + final_report_status, + margin_status + FROM mv_reports_ucepsa_demo.cost_readiness_orders_v1 + ORDER BY cost_readiness_score ASC, odoo_order_ref DESC + LIMIT %s + """, + (limit,), + ) + + raw_materials = fetch_all( + """ + SELECT + raw_product_default_code, + raw_product_name, + raw_uom, + affected_order_count, + affected_orders, + affected_sale_orders, + affected_customers, + total_consumed_qty, + current_material_cost_eur_per_kg, + raw_material_readiness_status, + recommended_action + FROM mv_reports_ucepsa_demo.cost_readiness_raw_materials_v1 + ORDER BY affected_order_count DESC, raw_product_default_code + """, + (), + ) + + return templates.TemplateResponse( + "cost_readiness.html", + { + "request": request, + "title": REPORT_TITLE, + "summary": summary or {}, + "orders": orders, + "raw_materials": raw_materials, + }, + ) + + +@app.get("/api/orders") +def api_orders(limit: int = Query(50, ge=1, le=200)): + rows = fetch_all( + """ + SELECT + odoo_order_ref, + odoo_state, + product, + machine_id, + start_ts, + end_ts, + window_min, + real_cycle_delta, + kwh_consumed, + stop_count, + stop_min, + technical_quality_level, + final_report_status + FROM mv_reports_ucepsa_demo.order_execution_report_full_v1 + ORDER BY start_ts DESC + LIMIT %s + """, + (limit,), + ) + return jsonable_encoder({"orders": rows}) + + +@app.get("/orders.html", response_class=HTMLResponse) +def orders_html(request: Request, limit: int = Query(50, ge=1, le=200)): + orders = fetch_all( + """ + SELECT + odoo_order_ref, + odoo_state, + product, + machine_id, + start_ts, + end_ts, + window_min, + real_cycle_delta, + kwh_consumed, + stop_count, + stop_min, + technical_quality_level, + final_report_status + FROM mv_reports_ucepsa_demo.order_execution_report_full_v1 + ORDER BY start_ts DESC + LIMIT %s + """, + (limit,), + ) + + return templates.TemplateResponse( + "orders_index.html", + { + "request": request, + "title": REPORT_TITLE, + "orders": orders, + }, + ) + diff --git a/ucepsa/edge-oee-demo/services/order-report-api/app/templates/cost_readiness.html b/ucepsa/edge-oee-demo/services/order-report-api/app/templates/cost_readiness.html new file mode 100644 index 0000000..cdc9e23 --- /dev/null +++ b/ucepsa/edge-oee-demo/services/order-report-api/app/templates/cost_readiness.html @@ -0,0 +1,250 @@ + + + + + {{ title }} - Cost Readiness + + + +
+ +

Cost Readiness Cockpit

+

{{ title }}

+

+ Esta pantalla muestra qué órdenes están preparadas para cálculo de margen y qué datos lo bloquean. + No calcula margen real si falta coste de materia prima. +

+ +
+
+
Órdenes totales
+
{{ summary.total_orders or 0 }}
+
+
+
Con ingreso
+
{{ summary.orders_with_revenue or 0 }}
+
+
+
Sin ingreso
+
{{ summary.orders_without_revenue or 0 }}
+
+
+
Sin coste material
+
{{ summary.orders_missing_raw_material_cost or 0 }}
+
+
+
Listas para margen
+
{{ summary.orders_ready_for_margin or 0 }}
+
+
+
Score medio
+
{{ summary.avg_cost_readiness_score|num(2) if summary.avg_cost_readiness_score is not none else "0.00" }}
+
+
+ +

1. Estado por orden

+ + + + + + + + + + + + + + + {% for order in orders %} + + + + + + + + + + + + + + {% else %} + + {% endfor %} +
OrdenPedido ventaClienteProductoMáquinaIngreso netoKg materia primaScoreEstadoBloqueo / acciónInforme
{{ order.odoo_order_ref }}{{ order.sale_order_ref or "" }}{{ order.customer_name or "" }} + {{ order.product_default_code or "" }}
+ {{ order.product_name or "" }} +
{{ order.machine_id }}{{ order.revenue_net_eur|num(2) if order.revenue_net_eur is not none else "" }} €{{ order.raw_total_consumed_qty|num(3) if order.raw_total_consumed_qty is not none else "" }} + {% if order.cost_readiness_score >= 80 %} + {{ order.cost_readiness_score }} + {% elif order.cost_readiness_score >= 50 %} + {{ order.cost_readiness_score }} + {% else %} + {{ order.cost_readiness_score }} + {% endif %} + + {% if order.cost_readiness_status == 'LISTO_PARA_MARGEN' %} + Listo para margen + {% elif order.cost_readiness_status == 'BLOQUEADO_FALTA_COSTE_MATERIA_PRIMA' %} + Falta coste materia prima + {% elif order.cost_readiness_status == 'BLOQUEADO_SIN_INGRESO' %} + Sin ingreso + {% elif order.cost_readiness_status == 'BLOQUEADO_DATOS_TECNICOS' %} + Datos técnicos débiles + {% else %} + {{ order.cost_readiness_status }} + {% endif %} + {{ order.cost_readiness_description }} + HTML + | + PDF +
No hay órdenes disponibles.
+ +

2. Materias primas pendientes de coste

+ + + + + + + + + + + + + + {% for raw in raw_materials %} + + + + + + + + + + + + + {% else %} + + {% endfor %} +
CódigoMateria primaUdMÓrdenes afectadasPedidos ventaClientesCantidad consumidaCoste €/kg actualEstadoAcción recomendada
{{ raw.raw_product_default_code or "" }}{{ raw.raw_product_name or "" }}{{ raw.raw_uom or "" }} + {{ raw.affected_order_count }}
+ {{ raw.affected_orders }} +
{{ raw.affected_sale_orders }}{{ raw.affected_customers }}{{ raw.total_consumed_qty|num(3) }}{{ raw.current_material_cost_eur_per_kg|num(4) if raw.current_material_cost_eur_per_kg is not none else "" }} + {% if raw.raw_material_readiness_status == 'COSTE_DISPONIBLE' %} + Coste disponible + {% else %} + Pendiente coste + {% endif %} + {{ raw.recommended_action }}
No hay materias primas detectadas.
+ +
+ + diff --git a/ucepsa/edge-oee-demo/services/order-report-api/app/templates/orders_index.html b/ucepsa/edge-oee-demo/services/order-report-api/app/templates/orders_index.html new file mode 100644 index 0000000..329fa43 --- /dev/null +++ b/ucepsa/edge-oee-demo/services/order-report-api/app/templates/orders_index.html @@ -0,0 +1,120 @@ + + + + + {{ title }} - Informes de orden + + + +
+

Informes de ejecución de orden

+

{{ title }}

+ +

+ Abrir Cost Readiness Cockpit +

+ + + + + + + + + + + + + + + + + + + {% for order in orders %} + + + + + + + + + + + + + + + + + {% else %} + + + + {% endfor %} +
OrdenEstadoProductoMáquinaInicioFinDuración minCicloskWhParosMin paroCalidadEstado informeAcciones
{{ order.odoo_order_ref }}{{ order.odoo_state }}{{ order.product }}{{ order.machine_id }}{{ order.start_ts|dt }}{{ order.end_ts|dt }}{{ order.window_min|num(2) }}{{ order.real_cycle_delta }}{{ order.kwh_consumed|num(3) }}{{ order.stop_count }}{{ order.stop_min|num(2) }} + {{ order.technical_quality_level }} + {{ order.final_report_status }} + HTML +  |  + PDF +
No hay órdenes disponibles.
+
+ + diff --git a/ucepsa/edge-oee-demo/sql/reports/080_margin_intelligence_cost_readiness.sql b/ucepsa/edge-oee-demo/sql/reports/080_margin_intelligence_cost_readiness.sql new file mode 100644 index 0000000..b9d6ee8 --- /dev/null +++ b/ucepsa/edge-oee-demo/sql/reports/080_margin_intelligence_cost_readiness.sql @@ -0,0 +1,1186 @@ +-- MESAVAULT Edge-OEE UCEPSA +-- Margin Intelligence + Cost Readiness Cockpit +-- Schema-only export. No data. No secrets. + +DROP VIEW IF EXISTS mv_reports_ucepsa_demo.cost_readiness_summary_v1; +DROP VIEW IF EXISTS mv_reports_ucepsa_demo.cost_readiness_raw_materials_v1; +DROP VIEW IF EXISTS mv_reports_ucepsa_demo.cost_readiness_orders_v1; + +DROP VIEW IF EXISTS mv_reports_ucepsa_demo.order_margin_report_v1; +DROP VIEW IF EXISTS mv_reports_ucepsa_demo.order_raw_material_costs_summary_v1; +DROP VIEW IF EXISTS mv_reports_ucepsa_demo.order_raw_material_costs_v1; + +DROP TABLE IF EXISTS mv_edge_oee_ucepsa_demo.raw_material_cost_parameters; +DROP TABLE IF EXISTS mv_edge_oee_ucepsa_demo.margin_cost_parameters; +DROP TABLE IF EXISTS mv_edge_oee_ucepsa_demo.odoo_order_commercials; + +-- +-- PostgreSQL database dump +-- + +\restrict prs5NerySlAyEA4mTL6Sg3Uqx00yOGe9h66uyBfmfApVdToFu1LKFfY74XjwGca + +-- Dumped from database version 16.14 +-- Dumped by pg_dump version 16.14 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: margin_cost_parameters; Type: TABLE; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE TABLE mv_edge_oee_ucepsa_demo.margin_cost_parameters ( + id bigint NOT NULL, + tenant text DEFAULT 'ucepsa'::text NOT NULL, + site text DEFAULT 'ucepsa_test'::text NOT NULL, + scope_level text DEFAULT 'GLOBAL'::text NOT NULL, + machine_id text, + product_default_code text, + energy_cost_eur_per_kwh numeric(14,6), + material_cost_eur_per_kg numeric(14,6), + machine_run_cost_eur_per_h numeric(14,4), + labor_cost_eur_per_h numeric(14,4), + overhead_cost_eur_per_h numeric(14,4), + valid_from timestamp with time zone DEFAULT now() NOT NULL, + valid_to timestamp with time zone, + is_active boolean DEFAULT true NOT NULL, + notes text, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: margin_cost_parameters_id_seq; Type: SEQUENCE; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE SEQUENCE mv_edge_oee_ucepsa_demo.margin_cost_parameters_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: margin_cost_parameters_id_seq; Type: SEQUENCE OWNED BY; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER SEQUENCE mv_edge_oee_ucepsa_demo.margin_cost_parameters_id_seq OWNED BY mv_edge_oee_ucepsa_demo.margin_cost_parameters.id; + + +-- +-- Name: odoo_order_commercials; Type: TABLE; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE TABLE mv_edge_oee_ucepsa_demo.odoo_order_commercials ( + id bigint NOT NULL, + tenant text DEFAULT 'ucepsa'::text NOT NULL, + site text DEFAULT 'ucepsa_test'::text NOT NULL, + odoo_instance text NOT NULL, + odoo_production_id integer NOT NULL, + odoo_order_ref text NOT NULL, + odoo_origin text, + odoo_state text, + product_id integer, + product_default_code text, + product_name text, + product_uom text, + product_standard_price numeric(14,4), + product_list_price numeric(14,4), + production_qty numeric(14,3), + qty_producing numeric(14,3), + production_uom text, + sale_line_id integer, + sale_order_id integer, + sale_order_ref text, + customer_id integer, + customer_name text, + sale_state text, + sale_date_order timestamp with time zone, + currency text, + sale_line_qty numeric(14,3), + sale_line_qty_delivered numeric(14,3), + sale_price_unit numeric(14,4), + sale_discount_pct numeric(10,2), + revenue_net_eur numeric(14,4), + revenue_total_eur numeric(14,4), + raw_material_value_eur numeric(14,4), + finished_value_eur numeric(14,4), + material_cost_status text DEFAULT 'UNAVAILABLE'::text NOT NULL, + stock_raw_moves_json jsonb DEFAULT '[]'::jsonb NOT NULL, + stock_finished_moves_json jsonb DEFAULT '[]'::jsonb NOT NULL, + raw_json jsonb DEFAULT '{}'::jsonb NOT NULL, + snapshot_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: odoo_order_commercials_id_seq; Type: SEQUENCE; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE SEQUENCE mv_edge_oee_ucepsa_demo.odoo_order_commercials_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: odoo_order_commercials_id_seq; Type: SEQUENCE OWNED BY; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER SEQUENCE mv_edge_oee_ucepsa_demo.odoo_order_commercials_id_seq OWNED BY mv_edge_oee_ucepsa_demo.odoo_order_commercials.id; + + +-- +-- Name: raw_material_cost_parameters; Type: TABLE; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE TABLE mv_edge_oee_ucepsa_demo.raw_material_cost_parameters ( + id bigint NOT NULL, + tenant text DEFAULT 'ucepsa'::text NOT NULL, + site text DEFAULT 'ucepsa_test'::text NOT NULL, + raw_product_default_code text NOT NULL, + raw_product_name text, + material_cost_eur_per_kg numeric(14,6) NOT NULL, + source text DEFAULT 'MANUAL'::text NOT NULL, + valid_from timestamp with time zone DEFAULT now() NOT NULL, + valid_to timestamp with time zone, + is_active boolean DEFAULT true NOT NULL, + notes text, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: raw_material_cost_parameters_id_seq; Type: SEQUENCE; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE SEQUENCE mv_edge_oee_ucepsa_demo.raw_material_cost_parameters_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: raw_material_cost_parameters_id_seq; Type: SEQUENCE OWNED BY; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER SEQUENCE mv_edge_oee_ucepsa_demo.raw_material_cost_parameters_id_seq OWNED BY mv_edge_oee_ucepsa_demo.raw_material_cost_parameters.id; + + +-- +-- Name: order_raw_material_costs_v1; Type: VIEW; Schema: mv_reports_ucepsa_demo; Owner: - +-- + +CREATE VIEW mv_reports_ucepsa_demo.order_raw_material_costs_v1 AS + WITH raw_moves AS ( + SELECT c.id AS commercial_row_id, + c.tenant, + c.site, + c.odoo_instance, + c.odoo_production_id, + c.odoo_order_ref, + c.sale_order_ref, + c.customer_name, + c.product_default_code AS finished_product_default_code, + c.product_name AS finished_product_name, + ((rm.raw_move ->> 'id'::text))::integer AS raw_move_id, + (NULLIF(((rm.raw_move -> 'product_id'::text) ->> 0), ''::text))::integer AS raw_product_id, + ((rm.raw_move -> 'product_id'::text) ->> 1) AS raw_product_name, + "substring"(((rm.raw_move -> 'product_id'::text) ->> 1), '\[([^\]]+)\]'::text) AS raw_product_default_code, + (NULLIF((rm.raw_move ->> 'product_uom_qty'::text), ''::text))::numeric AS planned_qty, + (NULLIF((rm.raw_move ->> 'quantity'::text), ''::text))::numeric AS consumed_qty, + ((rm.raw_move -> 'product_uom'::text) ->> 1) AS raw_uom, + (NULLIF((rm.raw_move ->> 'price_unit'::text), ''::text))::numeric AS odoo_price_unit, + (NULLIF((rm.raw_move ->> 'value'::text), ''::text))::numeric AS odoo_value, + (rm.raw_move ->> 'state'::text) AS move_state, + rm.raw_move AS raw_move_json + FROM (mv_edge_oee_ucepsa_demo.odoo_order_commercials c + CROSS JOIN LATERAL jsonb_array_elements(c.stock_raw_moves_json) rm(raw_move)) + ), costed AS ( + SELECT r.commercial_row_id, + r.tenant, + r.site, + r.odoo_instance, + r.odoo_production_id, + r.odoo_order_ref, + r.sale_order_ref, + r.customer_name, + r.finished_product_default_code, + r.finished_product_name, + r.raw_move_id, + r.raw_product_id, + r.raw_product_name, + r.raw_product_default_code, + r.planned_qty, + r.consumed_qty, + r.raw_uom, + r.odoo_price_unit, + r.odoo_value, + r.move_state, + r.raw_move_json, + p.id AS raw_material_cost_parameter_id, + p.material_cost_eur_per_kg, + p.source AS raw_material_cost_source, + p.notes AS raw_material_cost_notes, + CASE + WHEN (COALESCE(r.odoo_value, (0)::numeric) <> (0)::numeric) THEN abs(r.odoo_value) + WHEN ((lower(COALESCE(r.raw_uom, ''::text)) = 'kg'::text) AND (p.material_cost_eur_per_kg IS NOT NULL) AND (r.consumed_qty IS NOT NULL)) THEN (r.consumed_qty * p.material_cost_eur_per_kg) + ELSE NULL::numeric + END AS material_cost_estimated_eur, + CASE + WHEN (COALESCE(r.odoo_value, (0)::numeric) <> (0)::numeric) THEN 'FROM_ODOO_STOCK_MOVE_VALUE'::text + WHEN (r.raw_product_default_code IS NULL) THEN 'MISSING_RAW_PRODUCT_CODE'::text + WHEN (lower(COALESCE(r.raw_uom, ''::text)) <> 'kg'::text) THEN 'UNSUPPORTED_RAW_UOM'::text + WHEN (p.material_cost_eur_per_kg IS NULL) THEN 'MISSING_RAW_MATERIAL_COST_PARAMETER'::text + WHEN (r.consumed_qty IS NULL) THEN 'MISSING_CONSUMED_QTY'::text + ELSE 'FROM_RAW_MATERIAL_COST_PARAMETER'::text + END AS material_cost_status + FROM (raw_moves r + LEFT JOIN LATERAL ( SELECT p_1.id, + p_1.tenant, + p_1.site, + p_1.raw_product_default_code, + p_1.raw_product_name, + p_1.material_cost_eur_per_kg, + p_1.source, + p_1.valid_from, + p_1.valid_to, + p_1.is_active, + p_1.notes, + p_1.created_at + FROM mv_edge_oee_ucepsa_demo.raw_material_cost_parameters p_1 + WHERE ((p_1.tenant = r.tenant) AND (p_1.site = r.site) AND (p_1.is_active = true) AND (p_1.raw_product_default_code = r.raw_product_default_code) AND (p_1.valid_from <= now()) AND ((p_1.valid_to IS NULL) OR (p_1.valid_to > now()))) + ORDER BY p_1.valid_from DESC, p_1.id DESC + LIMIT 1) p ON (true)) + ) + SELECT commercial_row_id, + tenant, + site, + odoo_instance, + odoo_production_id, + odoo_order_ref, + sale_order_ref, + customer_name, + finished_product_default_code, + finished_product_name, + raw_move_id, + raw_product_id, + raw_product_name, + raw_product_default_code, + planned_qty, + consumed_qty, + raw_uom, + odoo_price_unit, + odoo_value, + move_state, + raw_move_json, + raw_material_cost_parameter_id, + material_cost_eur_per_kg, + raw_material_cost_source, + raw_material_cost_notes, + material_cost_estimated_eur, + material_cost_status, + round(material_cost_estimated_eur, 4) AS material_cost_estimated_eur_rounded + FROM costed; + + +-- +-- Name: order_raw_material_costs_summary_v1; Type: VIEW; Schema: mv_reports_ucepsa_demo; Owner: - +-- + +CREATE VIEW mv_reports_ucepsa_demo.order_raw_material_costs_summary_v1 AS + SELECT tenant, + site, + odoo_instance, + odoo_production_id, + odoo_order_ref, + sale_order_ref, + customer_name, + finished_product_default_code, + finished_product_name, + count(*) AS raw_material_line_count, + round(sum(COALESCE(consumed_qty, (0)::numeric)), 3) AS total_consumed_qty, + string_agg(concat(COALESCE(raw_product_default_code, 'sin_codigo'::text), ' - ', COALESCE(raw_product_name, 'sin_nombre'::text), ': ', COALESCE((consumed_qty)::text, 'sin_cantidad'::text), ' ', COALESCE(raw_uom, ''::text)), '; '::text ORDER BY raw_product_default_code) AS raw_materials_summary, + round(sum(COALESCE(material_cost_estimated_eur, (0)::numeric)), 4) AS material_cost_estimated_eur, + count(*) FILTER (WHERE (material_cost_estimated_eur IS NOT NULL)) AS costed_raw_material_line_count, + count(*) FILTER (WHERE ((material_cost_status <> 'FROM_RAW_MATERIAL_COST_PARAMETER'::text) AND (material_cost_status <> 'FROM_ODOO_STOCK_MOVE_VALUE'::text))) AS missing_or_invalid_raw_material_cost_count, + string_agg(DISTINCT material_cost_status, ', '::text ORDER BY material_cost_status) AS material_cost_status_summary + FROM mv_reports_ucepsa_demo.order_raw_material_costs_v1 + GROUP BY tenant, site, odoo_instance, odoo_production_id, odoo_order_ref, sale_order_ref, customer_name, finished_product_default_code, finished_product_name; + + +-- +-- Name: order_margin_report_v1; Type: VIEW; Schema: mv_reports_ucepsa_demo; Owner: - +-- + +CREATE VIEW mv_reports_ucepsa_demo.order_margin_report_v1 AS + WITH joined AS ( + SELECT r.order_row_id, + r.tenant, + r.site, + r.odoo_instance, + r.odoo_workorder_id, + r.odoo_production_id, + r.odoo_order_ref, + r.odoo_state, + r.machine_id, + r.asset, + r.product AS edge_product, + r.start_ts, + r.end_ts, + r.window_min, + r.run_min, + r.stop_min, + r.stop_cost_eur, + r.real_cycle_delta, + r.kwh_consumed, + r.kwh_per_cycle, + r.technical_quality_level, + r.final_report_status, + c.odoo_origin, + c.sale_order_ref, + c.customer_id, + c.customer_name, + c.sale_state, + c.sale_date_order, + c.currency, + c.product_id, + c.product_default_code, + c.product_name, + c.production_qty, + c.qty_producing, + c.production_uom, + c.sale_line_id, + c.sale_order_id, + c.sale_line_qty, + c.sale_line_qty_delivered, + c.sale_price_unit, + c.sale_discount_pct, + c.revenue_net_eur, + c.revenue_total_eur, + c.product_standard_price, + c.product_list_price, + c.raw_material_value_eur, + c.finished_value_eur, + c.material_cost_status AS odoo_material_cost_status, + c.snapshot_at AS commercial_snapshot_at, + rms.raw_material_line_count, + rms.total_consumed_qty AS raw_total_consumed_qty, + rms.raw_materials_summary, + rms.material_cost_estimated_eur AS raw_material_cost_estimated_eur, + rms.costed_raw_material_line_count, + rms.missing_or_invalid_raw_material_cost_count, + rms.material_cost_status_summary AS raw_material_cost_status_summary + FROM ((mv_reports_ucepsa_demo.order_execution_report_full_v1 r + LEFT JOIN mv_edge_oee_ucepsa_demo.odoo_order_commercials c ON (((c.odoo_instance = r.odoo_instance) AND (c.odoo_production_id = r.odoo_production_id)))) + LEFT JOIN mv_reports_ucepsa_demo.order_raw_material_costs_summary_v1 rms ON (((rms.odoo_instance = r.odoo_instance) AND (rms.odoo_production_id = r.odoo_production_id)))) + ), with_params AS ( + SELECT j.order_row_id, + j.tenant, + j.site, + j.odoo_instance, + j.odoo_workorder_id, + j.odoo_production_id, + j.odoo_order_ref, + j.odoo_state, + j.machine_id, + j.asset, + j.edge_product, + j.start_ts, + j.end_ts, + j.window_min, + j.run_min, + j.stop_min, + j.stop_cost_eur, + j.real_cycle_delta, + j.kwh_consumed, + j.kwh_per_cycle, + j.technical_quality_level, + j.final_report_status, + j.odoo_origin, + j.sale_order_ref, + j.customer_id, + j.customer_name, + j.sale_state, + j.sale_date_order, + j.currency, + j.product_id, + j.product_default_code, + j.product_name, + j.production_qty, + j.qty_producing, + j.production_uom, + j.sale_line_id, + j.sale_order_id, + j.sale_line_qty, + j.sale_line_qty_delivered, + j.sale_price_unit, + j.sale_discount_pct, + j.revenue_net_eur, + j.revenue_total_eur, + j.product_standard_price, + j.product_list_price, + j.raw_material_value_eur, + j.finished_value_eur, + j.odoo_material_cost_status, + j.commercial_snapshot_at, + j.raw_material_line_count, + j.raw_total_consumed_qty, + j.raw_materials_summary, + j.raw_material_cost_estimated_eur, + j.costed_raw_material_line_count, + j.missing_or_invalid_raw_material_cost_count, + j.raw_material_cost_status_summary, + p.id AS cost_parameter_id, + p.scope_level AS cost_parameter_scope_level, + p.energy_cost_eur_per_kwh, + p.material_cost_eur_per_kg AS fallback_material_cost_eur_per_kg, + p.machine_run_cost_eur_per_h, + p.labor_cost_eur_per_h, + p.overhead_cost_eur_per_h, + p.notes AS cost_parameter_notes + FROM (joined j + LEFT JOIN LATERAL ( SELECT p_1.id, + p_1.tenant, + p_1.site, + p_1.scope_level, + p_1.machine_id, + p_1.product_default_code, + p_1.energy_cost_eur_per_kwh, + p_1.material_cost_eur_per_kg, + p_1.machine_run_cost_eur_per_h, + p_1.labor_cost_eur_per_h, + p_1.overhead_cost_eur_per_h, + p_1.valid_from, + p_1.valid_to, + p_1.is_active, + p_1.notes, + p_1.created_at + FROM mv_edge_oee_ucepsa_demo.margin_cost_parameters p_1 + WHERE ((p_1.is_active = true) AND (p_1.tenant = j.tenant) AND (p_1.site = j.site) AND (p_1.valid_from <= now()) AND ((p_1.valid_to IS NULL) OR (p_1.valid_to > now())) AND ((p_1.machine_id IS NULL) OR (p_1.machine_id = j.machine_id)) AND ((p_1.product_default_code IS NULL) OR (p_1.product_default_code = j.product_default_code))) + ORDER BY + CASE + WHEN ((p_1.product_default_code IS NOT NULL) AND (p_1.machine_id IS NOT NULL)) THEN 1 + WHEN (p_1.product_default_code IS NOT NULL) THEN 2 + WHEN (p_1.machine_id IS NOT NULL) THEN 3 + ELSE 4 + END, p_1.valid_from DESC, p_1.id DESC + LIMIT 1) p ON (true)) + ), costs AS ( + SELECT with_params.order_row_id, + with_params.tenant, + with_params.site, + with_params.odoo_instance, + with_params.odoo_workorder_id, + with_params.odoo_production_id, + with_params.odoo_order_ref, + with_params.odoo_state, + with_params.machine_id, + with_params.asset, + with_params.edge_product, + with_params.start_ts, + with_params.end_ts, + with_params.window_min, + with_params.run_min, + with_params.stop_min, + with_params.stop_cost_eur, + with_params.real_cycle_delta, + with_params.kwh_consumed, + with_params.kwh_per_cycle, + with_params.technical_quality_level, + with_params.final_report_status, + with_params.odoo_origin, + with_params.sale_order_ref, + with_params.customer_id, + with_params.customer_name, + with_params.sale_state, + with_params.sale_date_order, + with_params.currency, + with_params.product_id, + with_params.product_default_code, + with_params.product_name, + with_params.production_qty, + with_params.qty_producing, + with_params.production_uom, + with_params.sale_line_id, + with_params.sale_order_id, + with_params.sale_line_qty, + with_params.sale_line_qty_delivered, + with_params.sale_price_unit, + with_params.sale_discount_pct, + with_params.revenue_net_eur, + with_params.revenue_total_eur, + with_params.product_standard_price, + with_params.product_list_price, + with_params.raw_material_value_eur, + with_params.finished_value_eur, + with_params.odoo_material_cost_status, + with_params.commercial_snapshot_at, + with_params.raw_material_line_count, + with_params.raw_total_consumed_qty, + with_params.raw_materials_summary, + with_params.raw_material_cost_estimated_eur, + with_params.costed_raw_material_line_count, + with_params.missing_or_invalid_raw_material_cost_count, + with_params.raw_material_cost_status_summary, + with_params.cost_parameter_id, + with_params.cost_parameter_scope_level, + with_params.energy_cost_eur_per_kwh, + with_params.fallback_material_cost_eur_per_kg, + with_params.machine_run_cost_eur_per_h, + with_params.labor_cost_eur_per_h, + with_params.overhead_cost_eur_per_h, + with_params.cost_parameter_notes, + COALESCE(NULLIF(with_params.qty_producing, (0)::numeric), NULLIF(with_params.production_qty, (0)::numeric)) AS costing_qty_kg, + CASE + WHEN ((with_params.raw_material_line_count > 0) AND (COALESCE(with_params.missing_or_invalid_raw_material_cost_count, (0)::bigint) = 0) AND (with_params.raw_material_cost_estimated_eur IS NOT NULL)) THEN with_params.raw_material_cost_estimated_eur + WHEN ((with_params.raw_material_line_count > 0) AND (COALESCE(with_params.missing_or_invalid_raw_material_cost_count, (0)::bigint) > 0)) THEN NULL::numeric + WHEN ((with_params.raw_material_value_eur IS NOT NULL) AND (with_params.raw_material_value_eur <> (0)::numeric)) THEN abs(with_params.raw_material_value_eur) + WHEN ((with_params.product_standard_price IS NOT NULL) AND (with_params.product_standard_price <> (0)::numeric) AND (COALESCE(NULLIF(with_params.qty_producing, (0)::numeric), NULLIF(with_params.production_qty, (0)::numeric)) IS NOT NULL)) THEN (with_params.product_standard_price * COALESCE(NULLIF(with_params.qty_producing, (0)::numeric), NULLIF(with_params.production_qty, (0)::numeric))) + WHEN ((with_params.fallback_material_cost_eur_per_kg IS NOT NULL) AND (COALESCE(NULLIF(with_params.qty_producing, (0)::numeric), NULLIF(with_params.production_qty, (0)::numeric)) IS NOT NULL)) THEN (with_params.fallback_material_cost_eur_per_kg * COALESCE(NULLIF(with_params.qty_producing, (0)::numeric), NULLIF(with_params.production_qty, (0)::numeric))) + ELSE NULL::numeric + END AS material_cost_estimated_eur, + CASE + WHEN ((with_params.raw_material_line_count > 0) AND (COALESCE(with_params.missing_or_invalid_raw_material_cost_count, (0)::bigint) = 0)) THEN 'FROM_RAW_MATERIAL_COSTS'::text + WHEN ((with_params.raw_material_line_count > 0) AND (COALESCE(with_params.missing_or_invalid_raw_material_cost_count, (0)::bigint) > 0)) THEN with_params.raw_material_cost_status_summary + WHEN ((with_params.raw_material_value_eur IS NOT NULL) AND (with_params.raw_material_value_eur <> (0)::numeric)) THEN 'FROM_ODOO_RAW_MATERIAL_VALUE'::text + WHEN ((with_params.product_standard_price IS NOT NULL) AND (with_params.product_standard_price <> (0)::numeric)) THEN 'FROM_PRODUCT_STANDARD_PRICE'::text + WHEN (with_params.fallback_material_cost_eur_per_kg IS NOT NULL) THEN 'FROM_FALLBACK_MARGIN_PARAMETER'::text + ELSE 'MISSING_MATERIAL_COST'::text + END AS material_cost_source_status, + CASE + WHEN ((with_params.kwh_consumed IS NOT NULL) AND (with_params.energy_cost_eur_per_kwh IS NOT NULL)) THEN (with_params.kwh_consumed * with_params.energy_cost_eur_per_kwh) + ELSE NULL::numeric + END AS energy_cost_estimated_eur, + CASE + WHEN ((with_params.run_min IS NOT NULL) AND (with_params.machine_run_cost_eur_per_h IS NOT NULL)) THEN ((with_params.run_min / 60.0) * with_params.machine_run_cost_eur_per_h) + ELSE NULL::numeric + END AS machine_run_cost_estimated_eur, + CASE + WHEN ((with_params.window_min IS NOT NULL) AND (with_params.labor_cost_eur_per_h IS NOT NULL)) THEN ((with_params.window_min / 60.0) * with_params.labor_cost_eur_per_h) + ELSE NULL::numeric + END AS labor_cost_estimated_eur, + CASE + WHEN ((with_params.window_min IS NOT NULL) AND (with_params.overhead_cost_eur_per_h IS NOT NULL)) THEN ((with_params.window_min / 60.0) * with_params.overhead_cost_eur_per_h) + ELSE NULL::numeric + END AS overhead_cost_estimated_eur + FROM with_params + ) + SELECT order_row_id, + tenant, + site, + odoo_instance, + odoo_workorder_id, + odoo_production_id, + odoo_order_ref, + odoo_state, + odoo_origin, + sale_order_ref, + customer_id, + customer_name, + sale_state, + sale_date_order, + currency, + machine_id, + asset, + product_id, + product_default_code, + COALESCE(product_name, edge_product) AS product_name, + production_qty, + qty_producing, + production_uom, + costing_qty_kg, + sale_line_id, + sale_order_id, + sale_line_qty, + sale_line_qty_delivered, + sale_price_unit, + sale_discount_pct, + revenue_net_eur, + revenue_total_eur, + start_ts, + end_ts, + window_min, + run_min, + stop_min, + stop_cost_eur, + real_cycle_delta, + kwh_consumed, + kwh_per_cycle, + product_standard_price, + product_list_price, + raw_material_value_eur, + finished_value_eur, + odoo_material_cost_status, + raw_material_line_count, + raw_total_consumed_qty, + raw_materials_summary, + raw_material_cost_status_summary, + costed_raw_material_line_count, + missing_or_invalid_raw_material_cost_count, + cost_parameter_id, + cost_parameter_scope_level, + energy_cost_eur_per_kwh, + fallback_material_cost_eur_per_kg, + machine_run_cost_eur_per_h, + labor_cost_eur_per_h, + overhead_cost_eur_per_h, + round(material_cost_estimated_eur, 4) AS material_cost_estimated_eur, + material_cost_source_status, + round(energy_cost_estimated_eur, 4) AS energy_cost_estimated_eur, + round(machine_run_cost_estimated_eur, 4) AS machine_run_cost_estimated_eur, + round(labor_cost_estimated_eur, 4) AS labor_cost_estimated_eur, + round(overhead_cost_estimated_eur, 4) AS overhead_cost_estimated_eur, + round((((((COALESCE(material_cost_estimated_eur, (0)::numeric) + COALESCE(energy_cost_estimated_eur, (0)::numeric)) + COALESCE(machine_run_cost_estimated_eur, (0)::numeric)) + COALESCE(labor_cost_estimated_eur, (0)::numeric)) + COALESCE(overhead_cost_estimated_eur, (0)::numeric)) + COALESCE(stop_cost_eur, (0)::numeric)), 4) AS known_or_estimated_cost_eur, + round( + CASE + WHEN (revenue_net_eur IS NOT NULL) THEN (revenue_net_eur - (((((COALESCE(material_cost_estimated_eur, (0)::numeric) + COALESCE(energy_cost_estimated_eur, (0)::numeric)) + COALESCE(machine_run_cost_estimated_eur, (0)::numeric)) + COALESCE(labor_cost_estimated_eur, (0)::numeric)) + COALESCE(overhead_cost_estimated_eur, (0)::numeric)) + COALESCE(stop_cost_eur, (0)::numeric))) + ELSE NULL::numeric + END, 4) AS partial_margin_after_known_costs_eur, + round( + CASE + WHEN ((revenue_net_eur IS NOT NULL) AND (material_cost_estimated_eur IS NOT NULL)) THEN (revenue_net_eur - (((((COALESCE(material_cost_estimated_eur, (0)::numeric) + COALESCE(energy_cost_estimated_eur, (0)::numeric)) + COALESCE(machine_run_cost_estimated_eur, (0)::numeric)) + COALESCE(labor_cost_estimated_eur, (0)::numeric)) + COALESCE(overhead_cost_estimated_eur, (0)::numeric)) + COALESCE(stop_cost_eur, (0)::numeric))) + ELSE NULL::numeric + END, 4) AS estimated_margin_eur, + round( + CASE + WHEN ((revenue_net_eur IS NOT NULL) AND (revenue_net_eur <> (0)::numeric) AND (material_cost_estimated_eur IS NOT NULL)) THEN (((revenue_net_eur - (((((COALESCE(material_cost_estimated_eur, (0)::numeric) + COALESCE(energy_cost_estimated_eur, (0)::numeric)) + COALESCE(machine_run_cost_estimated_eur, (0)::numeric)) + COALESCE(labor_cost_estimated_eur, (0)::numeric)) + COALESCE(overhead_cost_estimated_eur, (0)::numeric)) + COALESCE(stop_cost_eur, (0)::numeric))) / revenue_net_eur) * 100.0) + ELSE NULL::numeric + END, 2) AS estimated_margin_pct, + CASE + WHEN (revenue_net_eur IS NULL) THEN 'NO_REVENUE_LINKED'::text + ELSE 'REVENUE_LINKED'::text + END AS revenue_status, + CASE + WHEN ((raw_material_line_count > 0) AND (COALESCE(missing_or_invalid_raw_material_cost_count, (0)::bigint) > 0)) THEN 'MISSING_RAW_MATERIAL_COST_PARAMETER'::text + WHEN (material_cost_estimated_eur IS NULL) THEN 'MISSING_MATERIAL_COST'::text + WHEN (energy_cost_eur_per_kwh IS NULL) THEN 'MISSING_ENERGY_COST'::text + WHEN (machine_run_cost_eur_per_h IS NULL) THEN 'MISSING_MACHINE_RUN_COST'::text + ELSE 'COST_PARAMETERS_AVAILABLE'::text + END AS cost_status, + CASE + WHEN (revenue_net_eur IS NULL) THEN 'NO_MARGIN_WITHOUT_REVENUE'::text + WHEN ((raw_material_line_count > 0) AND (COALESCE(missing_or_invalid_raw_material_cost_count, (0)::bigint) > 0)) THEN 'PARTIAL_MARGIN_MISSING_RAW_MATERIAL_COST'::text + WHEN (material_cost_estimated_eur IS NULL) THEN 'PARTIAL_MARGIN_MISSING_MATERIAL'::text + ELSE 'ESTIMATED_MARGIN_AVAILABLE'::text + END AS margin_status, + technical_quality_level, + final_report_status, + commercial_snapshot_at + FROM costs; + + +-- +-- Name: cost_readiness_orders_v1; Type: VIEW; Schema: mv_reports_ucepsa_demo; Owner: - +-- + +CREATE VIEW mv_reports_ucepsa_demo.cost_readiness_orders_v1 AS + WITH base AS ( + SELECT m.order_row_id, + m.tenant, + m.site, + m.odoo_instance, + m.odoo_workorder_id, + m.odoo_production_id, + m.odoo_order_ref, + m.odoo_state, + m.odoo_origin, + m.sale_order_ref, + m.customer_id, + m.customer_name, + m.sale_state, + m.sale_date_order, + m.currency, + m.machine_id, + m.asset, + m.product_id, + m.product_default_code, + m.product_name, + m.production_qty, + m.qty_producing, + m.production_uom, + m.costing_qty_kg, + m.sale_line_id, + m.sale_order_id, + m.sale_line_qty, + m.sale_line_qty_delivered, + m.sale_price_unit, + m.sale_discount_pct, + m.revenue_net_eur, + m.revenue_total_eur, + m.start_ts, + m.end_ts, + m.window_min, + m.run_min, + m.stop_min, + m.stop_cost_eur, + m.real_cycle_delta, + m.kwh_consumed, + m.kwh_per_cycle, + m.product_standard_price, + m.product_list_price, + m.raw_material_value_eur, + m.finished_value_eur, + m.odoo_material_cost_status, + m.raw_material_line_count, + m.raw_total_consumed_qty, + m.raw_materials_summary, + m.raw_material_cost_status_summary, + m.costed_raw_material_line_count, + m.missing_or_invalid_raw_material_cost_count, + m.cost_parameter_id, + m.cost_parameter_scope_level, + m.energy_cost_eur_per_kwh, + m.fallback_material_cost_eur_per_kg, + m.machine_run_cost_eur_per_h, + m.labor_cost_eur_per_h, + m.overhead_cost_eur_per_h, + m.material_cost_estimated_eur, + m.material_cost_source_status, + m.energy_cost_estimated_eur, + m.machine_run_cost_estimated_eur, + m.labor_cost_estimated_eur, + m.overhead_cost_estimated_eur, + m.known_or_estimated_cost_eur, + m.partial_margin_after_known_costs_eur, + m.estimated_margin_eur, + m.estimated_margin_pct, + m.revenue_status, + m.cost_status, + m.margin_status, + m.technical_quality_level, + m.final_report_status, + m.commercial_snapshot_at, + CASE + WHEN (m.revenue_net_eur IS NOT NULL) THEN true + ELSE false + END AS has_revenue, + CASE + WHEN (NULLIF(TRIM(BOTH FROM COALESCE(m.customer_name, ''::text)), ''::text) IS NOT NULL) THEN true + ELSE false + END AS has_customer, + CASE + WHEN (NULLIF(TRIM(BOTH FROM COALESCE(m.sale_order_ref, ''::text)), ''::text) IS NOT NULL) THEN true + ELSE false + END AS has_sale_order, + CASE + WHEN (COALESCE(m.raw_material_line_count, (0)::bigint) > 0) THEN true + ELSE false + END AS has_raw_materials, + CASE + WHEN ((COALESCE(m.raw_material_line_count, (0)::bigint) > 0) AND (COALESCE(m.missing_or_invalid_raw_material_cost_count, (0)::bigint) = 0) AND (m.material_cost_estimated_eur IS NOT NULL)) THEN true + ELSE false + END AS has_raw_material_cost, + CASE + WHEN (m.technical_quality_level = ANY (ARRAY['FIABLE'::text, 'ACEPTABLE_CON_OBSERVACIONES'::text])) THEN true + ELSE false + END AS has_usable_technical_data, + CASE + WHEN (m.final_report_status = ANY (ARRAY['LISTO_INFORME_FINAL'::text, 'LISTO_TECNICO_REVISAR_CONVERSION'::text, 'LISTO_CON_OBSERVACIONES'::text])) THEN true + ELSE false + END AS has_closed_or_usable_order_report, + CASE + WHEN ((m.kwh_consumed IS NULL) OR (m.energy_cost_eur_per_kwh IS NOT NULL)) THEN true + ELSE false + END AS has_energy_cost_parameter, + CASE + WHEN ((m.run_min IS NULL) OR (m.machine_run_cost_eur_per_h IS NOT NULL)) THEN true + ELSE false + END AS has_machine_cost_parameter + FROM mv_reports_ucepsa_demo.order_margin_report_v1 m + ), scored AS ( + SELECT b.order_row_id, + b.tenant, + b.site, + b.odoo_instance, + b.odoo_workorder_id, + b.odoo_production_id, + b.odoo_order_ref, + b.odoo_state, + b.odoo_origin, + b.sale_order_ref, + b.customer_id, + b.customer_name, + b.sale_state, + b.sale_date_order, + b.currency, + b.machine_id, + b.asset, + b.product_id, + b.product_default_code, + b.product_name, + b.production_qty, + b.qty_producing, + b.production_uom, + b.costing_qty_kg, + b.sale_line_id, + b.sale_order_id, + b.sale_line_qty, + b.sale_line_qty_delivered, + b.sale_price_unit, + b.sale_discount_pct, + b.revenue_net_eur, + b.revenue_total_eur, + b.start_ts, + b.end_ts, + b.window_min, + b.run_min, + b.stop_min, + b.stop_cost_eur, + b.real_cycle_delta, + b.kwh_consumed, + b.kwh_per_cycle, + b.product_standard_price, + b.product_list_price, + b.raw_material_value_eur, + b.finished_value_eur, + b.odoo_material_cost_status, + b.raw_material_line_count, + b.raw_total_consumed_qty, + b.raw_materials_summary, + b.raw_material_cost_status_summary, + b.costed_raw_material_line_count, + b.missing_or_invalid_raw_material_cost_count, + b.cost_parameter_id, + b.cost_parameter_scope_level, + b.energy_cost_eur_per_kwh, + b.fallback_material_cost_eur_per_kg, + b.machine_run_cost_eur_per_h, + b.labor_cost_eur_per_h, + b.overhead_cost_eur_per_h, + b.material_cost_estimated_eur, + b.material_cost_source_status, + b.energy_cost_estimated_eur, + b.machine_run_cost_estimated_eur, + b.labor_cost_estimated_eur, + b.overhead_cost_estimated_eur, + b.known_or_estimated_cost_eur, + b.partial_margin_after_known_costs_eur, + b.estimated_margin_eur, + b.estimated_margin_pct, + b.revenue_status, + b.cost_status, + b.margin_status, + b.technical_quality_level, + b.final_report_status, + b.commercial_snapshot_at, + b.has_revenue, + b.has_customer, + b.has_sale_order, + b.has_raw_materials, + b.has_raw_material_cost, + b.has_usable_technical_data, + b.has_closed_or_usable_order_report, + b.has_energy_cost_parameter, + b.has_machine_cost_parameter, + GREATEST(0, (((((((((100 - + CASE + WHEN (b.has_revenue = false) THEN 35 + ELSE 0 + END) - + CASE + WHEN (b.has_customer = false) THEN 5 + ELSE 0 + END) - + CASE + WHEN (b.has_sale_order = false) THEN 5 + ELSE 0 + END) - + CASE + WHEN (b.has_usable_technical_data = false) THEN 25 + ELSE 0 + END) - + CASE + WHEN (b.has_closed_or_usable_order_report = false) THEN 15 + ELSE 0 + END) - + CASE + WHEN (b.has_raw_materials = false) THEN 20 + ELSE 0 + END) - + CASE + WHEN ((b.has_raw_materials = true) AND (b.has_raw_material_cost = false)) THEN 35 + ELSE 0 + END) - + CASE + WHEN (b.has_energy_cost_parameter = false) THEN 5 + ELSE 0 + END) - + CASE + WHEN (b.has_machine_cost_parameter = false) THEN 5 + ELSE 0 + END)) AS cost_readiness_score, + concat_ws('; '::text, + CASE + WHEN (b.has_revenue = false) THEN 'Falta ingreso de venta vinculado'::text + ELSE NULL::text + END, + CASE + WHEN (b.has_customer = false) THEN 'Falta cliente'::text + ELSE NULL::text + END, + CASE + WHEN (b.has_sale_order = false) THEN 'Falta pedido de venta'::text + ELSE NULL::text + END, + CASE + WHEN (b.has_usable_technical_data = false) THEN 'Datos técnicos no defendibles'::text + ELSE NULL::text + END, + CASE + WHEN (b.has_closed_or_usable_order_report = false) THEN 'Informe técnico parcial o bloqueado'::text + ELSE NULL::text + END, + CASE + WHEN (b.has_raw_materials = false) THEN 'No se detecta materia prima consumida'::text + ELSE NULL::text + END, + CASE + WHEN ((b.has_raw_materials = true) AND (b.has_raw_material_cost = false)) THEN 'Falta coste de materia prima'::text + ELSE NULL::text + END, + CASE + WHEN (b.has_energy_cost_parameter = false) THEN 'Falta coste €/kWh'::text + ELSE NULL::text + END, + CASE + WHEN (b.has_machine_cost_parameter = false) THEN 'Falta coste hora máquina'::text + ELSE NULL::text + END) AS cost_readiness_blockers + FROM base b + ) + SELECT order_row_id, + tenant, + site, + odoo_instance, + odoo_order_ref, + odoo_state, + sale_order_ref, + customer_name, + product_default_code, + product_name, + machine_id, + revenue_net_eur, + revenue_total_eur, + raw_material_line_count, + raw_total_consumed_qty, + raw_materials_summary, + raw_material_cost_status_summary, + missing_or_invalid_raw_material_cost_count, + material_cost_estimated_eur, + material_cost_source_status, + stop_cost_eur, + kwh_consumed, + run_min, + window_min, + technical_quality_level, + final_report_status, + margin_status, + has_revenue, + has_customer, + has_sale_order, + has_raw_materials, + has_raw_material_cost, + has_usable_technical_data, + has_closed_or_usable_order_report, + has_energy_cost_parameter, + has_machine_cost_parameter, + cost_readiness_score, + CASE + WHEN (has_revenue = false) THEN 'BLOQUEADO_SIN_INGRESO'::text + WHEN (has_usable_technical_data = false) THEN 'BLOQUEADO_DATOS_TECNICOS'::text + WHEN (has_closed_or_usable_order_report = false) THEN 'PARCIAL_INFORME_TECNICO'::text + WHEN (has_raw_materials = false) THEN 'BLOQUEADO_SIN_MATERIA_PRIMA'::text + WHEN ((has_raw_materials = true) AND (has_raw_material_cost = false)) THEN 'BLOQUEADO_FALTA_COSTE_MATERIA_PRIMA'::text + WHEN ((has_energy_cost_parameter = false) OR (has_machine_cost_parameter = false)) THEN 'PARCIAL_FALTAN_COSTES_OPERATIVOS'::text + ELSE 'LISTO_PARA_MARGEN'::text + END AS cost_readiness_status, + CASE + WHEN (cost_readiness_blockers = ''::text) THEN 'Orden lista para cálculo de margen estimado.'::text + ELSE cost_readiness_blockers + END AS cost_readiness_description + FROM scored; + + +-- +-- Name: cost_readiness_raw_materials_v1; Type: VIEW; Schema: mv_reports_ucepsa_demo; Owner: - +-- + +CREATE VIEW mv_reports_ucepsa_demo.cost_readiness_raw_materials_v1 AS + SELECT tenant, + site, + raw_product_default_code, + raw_product_name, + raw_uom, + count(DISTINCT odoo_order_ref) AS affected_order_count, + string_agg(DISTINCT odoo_order_ref, ', '::text ORDER BY odoo_order_ref) AS affected_orders, + string_agg(DISTINCT COALESCE(sale_order_ref, 'sin_pedido_venta'::text), ', '::text ORDER BY COALESCE(sale_order_ref, 'sin_pedido_venta'::text)) AS affected_sale_orders, + string_agg(DISTINCT COALESCE(customer_name, 'sin_cliente'::text), ', '::text ORDER BY COALESCE(customer_name, 'sin_cliente'::text)) AS affected_customers, + round(sum(COALESCE(consumed_qty, (0)::numeric)), 3) AS total_consumed_qty, + count(*) AS raw_material_line_count, + count(*) FILTER (WHERE (material_cost_status = 'MISSING_RAW_MATERIAL_COST_PARAMETER'::text)) AS missing_cost_line_count, + max(material_cost_eur_per_kg) AS current_material_cost_eur_per_kg, + CASE + WHEN (count(*) FILTER (WHERE (material_cost_status = 'MISSING_RAW_MATERIAL_COST_PARAMETER'::text)) > 0) THEN 'PENDIENTE_COSTE_MATERIA_PRIMA'::text + ELSE 'COSTE_DISPONIBLE'::text + END AS raw_material_readiness_status, + CASE + WHEN (count(*) FILTER (WHERE (material_cost_status = 'MISSING_RAW_MATERIAL_COST_PARAMETER'::text)) > 0) THEN concat('Falta coste €/kg para ', COALESCE(raw_product_default_code, 'sin_codigo'::text), ' - ', COALESCE(raw_product_name, 'sin_nombre'::text)) + ELSE 'Coste de materia prima disponible.'::text + END AS recommended_action + FROM mv_reports_ucepsa_demo.order_raw_material_costs_v1 + GROUP BY tenant, site, raw_product_default_code, raw_product_name, raw_uom; + + +-- +-- Name: cost_readiness_summary_v1; Type: VIEW; Schema: mv_reports_ucepsa_demo; Owner: - +-- + +CREATE VIEW mv_reports_ucepsa_demo.cost_readiness_summary_v1 AS + SELECT tenant, + site, + count(*) AS total_orders, + count(*) FILTER (WHERE (has_revenue = true)) AS orders_with_revenue, + count(*) FILTER (WHERE (has_revenue = false)) AS orders_without_revenue, + count(*) FILTER (WHERE (has_raw_materials = true)) AS orders_with_raw_materials, + count(*) FILTER (WHERE (has_raw_materials = false)) AS orders_without_raw_materials, + count(*) FILTER (WHERE ((has_raw_materials = true) AND (has_raw_material_cost = false))) AS orders_missing_raw_material_cost, + count(*) FILTER (WHERE (has_usable_technical_data = false)) AS orders_with_bad_technical_data, + count(*) FILTER (WHERE (cost_readiness_status = 'LISTO_PARA_MARGEN'::text)) AS orders_ready_for_margin, + count(*) FILTER (WHERE (cost_readiness_status = 'BLOQUEADO_FALTA_COSTE_MATERIA_PRIMA'::text)) AS orders_blocked_missing_raw_material_cost, + count(*) FILTER (WHERE (cost_readiness_status = 'BLOQUEADO_SIN_INGRESO'::text)) AS orders_blocked_no_revenue, + round(avg(cost_readiness_score), 2) AS avg_cost_readiness_score, + min(cost_readiness_score) AS min_cost_readiness_score, + max(cost_readiness_score) AS max_cost_readiness_score + FROM mv_reports_ucepsa_demo.cost_readiness_orders_v1 + GROUP BY tenant, site; + + +-- +-- Name: margin_cost_parameters id; Type: DEFAULT; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER TABLE ONLY mv_edge_oee_ucepsa_demo.margin_cost_parameters ALTER COLUMN id SET DEFAULT nextval('mv_edge_oee_ucepsa_demo.margin_cost_parameters_id_seq'::regclass); + + +-- +-- Name: odoo_order_commercials id; Type: DEFAULT; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER TABLE ONLY mv_edge_oee_ucepsa_demo.odoo_order_commercials ALTER COLUMN id SET DEFAULT nextval('mv_edge_oee_ucepsa_demo.odoo_order_commercials_id_seq'::regclass); + + +-- +-- Name: raw_material_cost_parameters id; Type: DEFAULT; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER TABLE ONLY mv_edge_oee_ucepsa_demo.raw_material_cost_parameters ALTER COLUMN id SET DEFAULT nextval('mv_edge_oee_ucepsa_demo.raw_material_cost_parameters_id_seq'::regclass); + + +-- +-- Name: margin_cost_parameters margin_cost_parameters_pkey; Type: CONSTRAINT; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER TABLE ONLY mv_edge_oee_ucepsa_demo.margin_cost_parameters + ADD CONSTRAINT margin_cost_parameters_pkey PRIMARY KEY (id); + + +-- +-- Name: odoo_order_commercials odoo_order_commercials_pkey; Type: CONSTRAINT; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER TABLE ONLY mv_edge_oee_ucepsa_demo.odoo_order_commercials + ADD CONSTRAINT odoo_order_commercials_pkey PRIMARY KEY (id); + + +-- +-- Name: raw_material_cost_parameters raw_material_cost_parameters_pkey; Type: CONSTRAINT; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER TABLE ONLY mv_edge_oee_ucepsa_demo.raw_material_cost_parameters + ADD CONSTRAINT raw_material_cost_parameters_pkey PRIMARY KEY (id); + + +-- +-- Name: odoo_order_commercials ux_odoo_order_commercials_instance_production; Type: CONSTRAINT; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +ALTER TABLE ONLY mv_edge_oee_ucepsa_demo.odoo_order_commercials + ADD CONSTRAINT ux_odoo_order_commercials_instance_production UNIQUE (odoo_instance, odoo_production_id); + + +-- +-- Name: idx_margin_cost_parameters_scope; Type: INDEX; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE INDEX idx_margin_cost_parameters_scope ON mv_edge_oee_ucepsa_demo.margin_cost_parameters USING btree (tenant, site, is_active, machine_id, product_default_code); + + +-- +-- Name: idx_odoo_order_commercials_customer; Type: INDEX; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE INDEX idx_odoo_order_commercials_customer ON mv_edge_oee_ucepsa_demo.odoo_order_commercials USING btree (customer_name); + + +-- +-- Name: idx_odoo_order_commercials_order_ref; Type: INDEX; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE INDEX idx_odoo_order_commercials_order_ref ON mv_edge_oee_ucepsa_demo.odoo_order_commercials USING btree (odoo_order_ref); + + +-- +-- Name: idx_odoo_order_commercials_sale_order_ref; Type: INDEX; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE INDEX idx_odoo_order_commercials_sale_order_ref ON mv_edge_oee_ucepsa_demo.odoo_order_commercials USING btree (sale_order_ref); + + +-- +-- Name: idx_raw_material_cost_parameters_lookup; Type: INDEX; Schema: mv_edge_oee_ucepsa_demo; Owner: - +-- + +CREATE INDEX idx_raw_material_cost_parameters_lookup ON mv_edge_oee_ucepsa_demo.raw_material_cost_parameters USING btree (tenant, site, raw_product_default_code, is_active, valid_from DESC); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict prs5NerySlAyEA4mTL6Sg3Uqx00yOGe9h66uyBfmfApVdToFu1LKFfY74XjwGca + + +-- Read-only grants for Grafana / reporting API user +GRANT USAGE ON SCHEMA mv_edge_oee_ucepsa_demo TO grafana_ucepsa_ro; +GRANT USAGE ON SCHEMA mv_reports_ucepsa_demo TO grafana_ucepsa_ro; + +GRANT SELECT ON mv_edge_oee_ucepsa_demo.odoo_order_commercials TO grafana_ucepsa_ro; +GRANT SELECT ON mv_edge_oee_ucepsa_demo.margin_cost_parameters TO grafana_ucepsa_ro; +GRANT SELECT ON mv_edge_oee_ucepsa_demo.raw_material_cost_parameters TO grafana_ucepsa_ro; + +GRANT SELECT ON mv_reports_ucepsa_demo.order_raw_material_costs_v1 TO grafana_ucepsa_ro; +GRANT SELECT ON mv_reports_ucepsa_demo.order_raw_material_costs_summary_v1 TO grafana_ucepsa_ro; +GRANT SELECT ON mv_reports_ucepsa_demo.order_margin_report_v1 TO grafana_ucepsa_ro; + +GRANT SELECT ON mv_reports_ucepsa_demo.cost_readiness_orders_v1 TO grafana_ucepsa_ro; +GRANT SELECT ON mv_reports_ucepsa_demo.cost_readiness_raw_materials_v1 TO grafana_ucepsa_ro; +GRANT SELECT ON mv_reports_ucepsa_demo.cost_readiness_summary_v1 TO grafana_ucepsa_ro;