fix(ucepsa): exclude pending Shop Floor planning dates
This commit is contained in:
parent
0ed49a3838
commit
86fc845a5e
@ -0,0 +1,57 @@
|
|||||||
|
# UCEPSA — Guard READY/PENDING Shop Floor v0.3.3.1
|
||||||
|
|
||||||
|
## Hallazgo
|
||||||
|
|
||||||
|
Durante la primera sincronización continua apareció:
|
||||||
|
|
||||||
|
```text
|
||||||
|
workorder 434
|
||||||
|
state = ready
|
||||||
|
production_state = progress
|
||||||
|
date_start = 17/07/2026
|
||||||
|
date_finished = 04/09/2026
|
||||||
|
```
|
||||||
|
|
||||||
|
Odoo puede poblar esas fechas como planificación antes de que exista una sesión
|
||||||
|
real de operaria. La v0.3.3 las interpretó como intervalo y mostró 1.182 horas
|
||||||
|
de cobertura ficticia.
|
||||||
|
|
||||||
|
## Riesgo
|
||||||
|
|
||||||
|
El modo sigue siendo SHADOW y las sesiones aún no alimentan candidatos de paro,
|
||||||
|
por lo que no hubo impacto oficial. Aun así, un workorder `READY/PENDING` no debe
|
||||||
|
aportar:
|
||||||
|
|
||||||
|
- contexto productivo;
|
||||||
|
- horas de cobertura;
|
||||||
|
- atribución individual;
|
||||||
|
- cola de revisión de sesiones reales.
|
||||||
|
|
||||||
|
## Corrección
|
||||||
|
|
||||||
|
La v0.3.3.1:
|
||||||
|
|
||||||
|
1. conserva las fechas originales de Odoo en
|
||||||
|
`mv_hot.odoo_shopfloor_workorder_current`;
|
||||||
|
2. pone `started_at` y `ended_at` de la sesión a `NULL` mientras el estado no sea
|
||||||
|
`progress` o `done`;
|
||||||
|
3. exige `OPEN` o `CLOSED` para `context_time_usable`;
|
||||||
|
4. excluye `PENDING` de la cola de revisión;
|
||||||
|
5. excluye `PENDING` del cálculo de horas;
|
||||||
|
6. mantiene `official_eligible=false`.
|
||||||
|
|
||||||
|
## Orden de despliegue
|
||||||
|
|
||||||
|
1. Detener temporalmente el sincronizador.
|
||||||
|
2. Aplicar migración 105.
|
||||||
|
3. Instalar el sincronizador v0.3.3.1.
|
||||||
|
4. Refrescar explícitamente el workorder 434.
|
||||||
|
5. Validar.
|
||||||
|
6. Arrancar de nuevo el sincronizador.
|
||||||
|
|
||||||
|
## Observación de producción del lunes
|
||||||
|
|
||||||
|
Cuando 434 u otro workorder pase de `ready` a `progress`, el sincronizador
|
||||||
|
registrará la transición. Después debe comprobarse si Odoo actualiza
|
||||||
|
`date_start` al inicio real o conserva la fecha planificada. Esa observación
|
||||||
|
determinará la regla definitiva de inicio para v0.3.4.
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
\pset pager off
|
||||||
|
|
||||||
|
\echo '=== 1. Estado del sincronizador ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
tenant,
|
||||||
|
site,
|
||||||
|
sync_name,
|
||||||
|
last_success_at,
|
||||||
|
last_odoo_cursor_at,
|
||||||
|
last_processed_count,
|
||||||
|
last_error,
|
||||||
|
updated_at
|
||||||
|
FROM mv_loss_intelligence.odoo_shopfloor_sync_state
|
||||||
|
WHERE sync_name = 'odoo_shopfloor_sessions_v033';
|
||||||
|
|
||||||
|
\echo '=== 2. Workorder 434 no es sesión productiva ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
odoo_workorder_id,
|
||||||
|
machine_id,
|
||||||
|
production_order,
|
||||||
|
workorder_state,
|
||||||
|
production_state,
|
||||||
|
session_status,
|
||||||
|
started_at,
|
||||||
|
ended_at,
|
||||||
|
confidence,
|
||||||
|
context_time_usable,
|
||||||
|
individual_attribution_eligible,
|
||||||
|
official_eligible
|
||||||
|
FROM mv_reports_ucepsa_prod.li_odoo_shopfloor_sessions_v1
|
||||||
|
WHERE odoo_workorder_id = 434;
|
||||||
|
|
||||||
|
\echo '=== 3. Workorder 434 no aparece como contexto ==='
|
||||||
|
|
||||||
|
SELECT count(*) AS pending_context_rows
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_odoo_shopfloor_context_intervals_v1
|
||||||
|
WHERE odoo_workorder_id = 434;
|
||||||
|
|
||||||
|
\echo '=== 4. Workorder 434 no aparece en revisión de sesiones ==='
|
||||||
|
|
||||||
|
SELECT count(*) AS pending_review_rows
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_odoo_shopfloor_session_review_queue_v1
|
||||||
|
WHERE odoo_workorder_id = 434;
|
||||||
|
|
||||||
|
\echo '=== 5. READY conserva fechas fuente en CURRENT ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
odoo_workorder_id,
|
||||||
|
workorder_state,
|
||||||
|
workorder_started_at,
|
||||||
|
workorder_ended_at,
|
||||||
|
observed_at,
|
||||||
|
updated_at
|
||||||
|
FROM mv_hot.odoo_shopfloor_workorder_current
|
||||||
|
WHERE odoo_workorder_id = 434;
|
||||||
|
|
||||||
|
\echo '=== 6. Cobertura CORT-01 sin horas ficticias ==='
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
mv_reports_ucepsa_prod
|
||||||
|
.li_odoo_shopfloor_session_coverage_v1
|
||||||
|
WHERE machine_id = 'CORT-01';
|
||||||
|
|
||||||
|
\echo '=== 7. Sesiones conocidas no cambian ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
odoo_workorder_id,
|
||||||
|
production_order,
|
||||||
|
session_status,
|
||||||
|
started_at,
|
||||||
|
ended_at,
|
||||||
|
operator_employee_id,
|
||||||
|
operator_candidate_employee_id,
|
||||||
|
declared_quantity,
|
||||||
|
declared_quantity_candidate,
|
||||||
|
confidence,
|
||||||
|
context_time_usable,
|
||||||
|
official_eligible
|
||||||
|
FROM mv_reports_ucepsa_prod.li_odoo_shopfloor_sessions_v1
|
||||||
|
WHERE odoo_workorder_id IN (87, 810)
|
||||||
|
ORDER BY odoo_workorder_id;
|
||||||
|
|
||||||
|
\echo '=== 8. Invariantes ==='
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE official_eligible
|
||||||
|
) AS official_rows,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE session_status = 'PENDING'
|
||||||
|
AND (
|
||||||
|
started_at IS NOT NULL
|
||||||
|
OR ended_at IS NOT NULL
|
||||||
|
)
|
||||||
|
) AS pending_rows_with_session_time,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE ended_at IS NOT NULL
|
||||||
|
AND started_at IS NOT NULL
|
||||||
|
AND ended_at < started_at
|
||||||
|
) AS invalid_intervals
|
||||||
|
FROM mv_loss_intelligence.odoo_shopfloor_sessions;
|
||||||
|
|
||||||
|
\echo '=== 9. Cola oficial sigue vacía ==='
|
||||||
|
|
||||||
|
SELECT count(*) AS official_operator_queue_rows
|
||||||
|
FROM mv_reports_ucepsa_prod.li_operator_stop_queue_v2;
|
||||||
@ -0,0 +1,223 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_odoo_shopfloor_sessions_v1
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
s.shopfloor_session_id,
|
||||||
|
s.tenant,
|
||||||
|
s.site,
|
||||||
|
s.session_key,
|
||||||
|
s.production_chain_key,
|
||||||
|
s.machine_id,
|
||||||
|
s.workcenter_id,
|
||||||
|
s.workcenter_name,
|
||||||
|
s.odoo_production_id,
|
||||||
|
s.production_order,
|
||||||
|
s.odoo_workorder_id,
|
||||||
|
s.workorder_name,
|
||||||
|
s.workorder_state,
|
||||||
|
s.production_state,
|
||||||
|
s.origin,
|
||||||
|
s.product_id,
|
||||||
|
s.product_name,
|
||||||
|
s.product_uom_id,
|
||||||
|
s.product_uom_name,
|
||||||
|
s.started_at,
|
||||||
|
s.ended_at,
|
||||||
|
s.session_status,
|
||||||
|
s.operator_employee_id,
|
||||||
|
s.operator_name,
|
||||||
|
s.operator_candidate_employee_id,
|
||||||
|
s.operator_candidate_name,
|
||||||
|
s.operator_resolution,
|
||||||
|
s.operator_confidence,
|
||||||
|
s.operator_confirmation_status,
|
||||||
|
s.planned_quantity,
|
||||||
|
s.declared_quantity,
|
||||||
|
s.declared_quantity_candidate,
|
||||||
|
s.posted_quantity,
|
||||||
|
s.remaining_planned_quantity,
|
||||||
|
s.quantity_status,
|
||||||
|
s.quantity_confirmation_status,
|
||||||
|
s.confidence,
|
||||||
|
s.official_eligible,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
s.session_status = 'OPEN'
|
||||||
|
AND s.started_at IS NOT NULL
|
||||||
|
AND s.ended_at IS NULL
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
s.session_status = 'CLOSED'
|
||||||
|
AND s.started_at IS NOT NULL
|
||||||
|
AND s.ended_at IS NOT NULL
|
||||||
|
)
|
||||||
|
) AS context_time_usable,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
s.session_status = 'OPEN'
|
||||||
|
AND s.started_at IS NOT NULL
|
||||||
|
AND s.ended_at IS NULL
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
s.session_status = 'CLOSED'
|
||||||
|
AND s.started_at IS NOT NULL
|
||||||
|
AND s.ended_at IS NOT NULL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND s.operator_employee_id IS NOT NULL
|
||||||
|
AND s.operator_confirmation_status IN (
|
||||||
|
'EVIDENCE_CONFIRMED',
|
||||||
|
'CONFIRMED_HUMAN'
|
||||||
|
)
|
||||||
|
) AS individual_attribution_eligible,
|
||||||
|
s.evidence_json,
|
||||||
|
s.reviewed_by,
|
||||||
|
s.reviewed_at,
|
||||||
|
s.review_notes,
|
||||||
|
s.source_observed_at,
|
||||||
|
s.source_odoo_write_date,
|
||||||
|
s.created_at,
|
||||||
|
s.updated_at
|
||||||
|
FROM mv_loss_intelligence.odoo_shopfloor_sessions s;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_odoo_shopfloor_sessions_v1 IS
|
||||||
|
'Sesiones canónicas Odoo Shop Floor SHADOW. READY/PENDING no aporta tiempo productivo aunque Odoo traiga fechas planificadas.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_odoo_shopfloor_session_review_queue_v1
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
row_number() OVER (
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN s.session_status = 'OPEN' THEN 0
|
||||||
|
ELSE 1
|
||||||
|
END,
|
||||||
|
COALESCE(
|
||||||
|
s.ended_at,
|
||||||
|
s.started_at,
|
||||||
|
s.updated_at
|
||||||
|
) DESC,
|
||||||
|
s.shopfloor_session_id DESC
|
||||||
|
) AS review_rank,
|
||||||
|
s.*
|
||||||
|
FROM mv_reports_ucepsa_prod.li_odoo_shopfloor_sessions_v1 s
|
||||||
|
WHERE s.session_status IN ('OPEN', 'CLOSED')
|
||||||
|
AND (
|
||||||
|
s.operator_confirmation_status IN (
|
||||||
|
'PENDING_HUMAN',
|
||||||
|
'UNRESOLVED',
|
||||||
|
'REJECTED'
|
||||||
|
)
|
||||||
|
OR s.quantity_confirmation_status IN (
|
||||||
|
'PENDING_HUMAN',
|
||||||
|
'UNRESOLVED'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_odoo_shopfloor_session_review_queue_v1 IS
|
||||||
|
'Cola SHADOW de sesiones OPEN/CLOSED con operaria o cantidad pendiente. Excluye workorders READY/PENDING.';
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_odoo_shopfloor_session_coverage_v1
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
s.tenant,
|
||||||
|
s.site,
|
||||||
|
s.machine_id,
|
||||||
|
count(*) AS session_count,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE s.session_status = 'OPEN'
|
||||||
|
) AS open_session_count,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE s.session_status = 'CLOSED'
|
||||||
|
) AS closed_session_count,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE s.session_status IN ('OPEN', 'CLOSED')
|
||||||
|
AND s.operator_confirmation_status IN (
|
||||||
|
'EVIDENCE_CONFIRMED',
|
||||||
|
'CONFIRMED_HUMAN'
|
||||||
|
)
|
||||||
|
) AS operator_confirmed_count,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE s.session_status IN ('OPEN', 'CLOSED')
|
||||||
|
AND s.operator_confirmation_status =
|
||||||
|
'PENDING_HUMAN'
|
||||||
|
) AS operator_pending_count,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE s.session_status IN ('OPEN', 'CLOSED')
|
||||||
|
AND s.quantity_confirmation_status IN (
|
||||||
|
'EVIDENCE_CONFIRMED',
|
||||||
|
'CONFIRMED_HUMAN'
|
||||||
|
)
|
||||||
|
) AS quantity_confirmed_count,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE s.session_status IN ('OPEN', 'CLOSED')
|
||||||
|
AND s.quantity_confirmation_status =
|
||||||
|
'PENDING_HUMAN'
|
||||||
|
) AS quantity_pending_count,
|
||||||
|
COALESCE(
|
||||||
|
round(
|
||||||
|
(
|
||||||
|
sum(
|
||||||
|
EXTRACT(
|
||||||
|
EPOCH FROM (
|
||||||
|
COALESCE(s.ended_at, now())
|
||||||
|
- s.started_at
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) FILTER (
|
||||||
|
WHERE (
|
||||||
|
s.session_status = 'OPEN'
|
||||||
|
AND s.started_at IS NOT NULL
|
||||||
|
AND s.ended_at IS NULL
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
s.session_status = 'CLOSED'
|
||||||
|
AND s.started_at IS NOT NULL
|
||||||
|
AND s.ended_at IS NOT NULL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)::numeric / 3600,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
0::numeric
|
||||||
|
) AS covered_hours
|
||||||
|
FROM mv_loss_intelligence.odoo_shopfloor_sessions s
|
||||||
|
GROUP BY
|
||||||
|
s.tenant,
|
||||||
|
s.site,
|
||||||
|
s.machine_id;
|
||||||
|
|
||||||
|
COMMENT ON VIEW
|
||||||
|
mv_reports_ucepsa_prod.li_odoo_shopfloor_session_coverage_v1 IS
|
||||||
|
'Cobertura de sesiones OPEN/CLOSED. Las fechas planificadas de READY/PENDING no suman horas.';
|
||||||
|
|
||||||
|
UPDATE mv_loss_intelligence.odoo_shopfloor_sessions
|
||||||
|
SET
|
||||||
|
started_at = NULL,
|
||||||
|
ended_at = NULL,
|
||||||
|
confidence = 'LOW',
|
||||||
|
evidence_json = evidence_json || jsonb_build_object(
|
||||||
|
'_mesavault_guard_v0331',
|
||||||
|
jsonb_build_object(
|
||||||
|
'reason',
|
||||||
|
'READY/PENDING dates are planning metadata, not session time',
|
||||||
|
'guarded_at',
|
||||||
|
now()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
updated_at = now()
|
||||||
|
WHERE session_status = 'PENDING'
|
||||||
|
AND (
|
||||||
|
started_at IS NOT NULL
|
||||||
|
OR ended_at IS NOT NULL
|
||||||
|
OR confidence <> 'LOW'
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@ -24,7 +24,7 @@ import psycopg2
|
|||||||
from psycopg2.extras import Json, RealDictCursor
|
from psycopg2.extras import Json, RealDictCursor
|
||||||
|
|
||||||
|
|
||||||
VERSION = "0.3.3"
|
VERSION = "0.3.3.1"
|
||||||
SYNC_NAME = "odoo_shopfloor_sessions_v033"
|
SYNC_NAME = "odoo_shopfloor_sessions_v033"
|
||||||
RUNNING = True
|
RUNNING = True
|
||||||
|
|
||||||
@ -851,21 +851,36 @@ def build_records(
|
|||||||
output_moves,
|
output_moves,
|
||||||
)
|
)
|
||||||
|
|
||||||
started_at = parse_odoo_datetime(
|
source_workorder_started_at = parse_odoo_datetime(
|
||||||
workorder.get("date_start")
|
workorder.get("date_start")
|
||||||
)
|
)
|
||||||
ended_at = parse_odoo_datetime(
|
source_workorder_ended_at = parse_odoo_datetime(
|
||||||
workorder.get("date_finished")
|
workorder.get("date_finished")
|
||||||
)
|
)
|
||||||
workorder_state = str(workorder.get("state") or "")
|
workorder_state = str(workorder.get("state") or "")
|
||||||
|
|
||||||
if workorder_state == "done" and started_at and ended_at:
|
# En Odoo, los workorders READY pueden traer fechas de
|
||||||
|
# planificación, incluso una fecha de fin futura. Esas fechas
|
||||||
|
# no representan una sesión real de operaria.
|
||||||
|
if (
|
||||||
|
workorder_state == "done"
|
||||||
|
and source_workorder_started_at
|
||||||
|
and source_workorder_ended_at
|
||||||
|
):
|
||||||
session_status = "CLOSED"
|
session_status = "CLOSED"
|
||||||
elif workorder_state == "progress" and started_at:
|
started_at = source_workorder_started_at
|
||||||
|
ended_at = source_workorder_ended_at
|
||||||
|
elif (
|
||||||
|
workorder_state == "progress"
|
||||||
|
and source_workorder_started_at
|
||||||
|
):
|
||||||
session_status = "OPEN"
|
session_status = "OPEN"
|
||||||
|
started_at = source_workorder_started_at
|
||||||
ended_at = None
|
ended_at = None
|
||||||
else:
|
else:
|
||||||
session_status = "PENDING"
|
session_status = "PENDING"
|
||||||
|
started_at = None
|
||||||
|
ended_at = None
|
||||||
|
|
||||||
operator_confirmed = (
|
operator_confirmed = (
|
||||||
operator["operator_confirmation_status"]
|
operator["operator_confirmation_status"]
|
||||||
@ -876,10 +891,15 @@ def build_records(
|
|||||||
== "EVIDENCE_CONFIRMED"
|
== "EVIDENCE_CONFIRMED"
|
||||||
)
|
)
|
||||||
complete_interval = (
|
complete_interval = (
|
||||||
started_at is not None
|
(
|
||||||
and (
|
|
||||||
session_status == "OPEN"
|
session_status == "OPEN"
|
||||||
or ended_at is not None
|
and started_at is not None
|
||||||
|
and ended_at is None
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
session_status == "CLOSED"
|
||||||
|
and started_at is not None
|
||||||
|
and ended_at is not None
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -920,6 +940,13 @@ def build_records(
|
|||||||
"output_moves": output_moves,
|
"output_moves": output_moves,
|
||||||
"operator_resolution": operator,
|
"operator_resolution": operator,
|
||||||
"quantity_resolution": quantity,
|
"quantity_resolution": quantity,
|
||||||
|
"source_workorder_datetimes": {
|
||||||
|
"date_start": source_workorder_started_at,
|
||||||
|
"date_finished": source_workorder_ended_at,
|
||||||
|
"semantic_rule": (
|
||||||
|
"READY dates are planning metadata, not session time"
|
||||||
|
),
|
||||||
|
},
|
||||||
"timezone_rule": (
|
"timezone_rule": (
|
||||||
"Odoo XML-RPC datetimes interpreted as UTC"
|
"Odoo XML-RPC datetimes interpreted as UTC"
|
||||||
),
|
),
|
||||||
@ -937,6 +964,12 @@ def build_records(
|
|||||||
"product_id": product_id,
|
"product_id": product_id,
|
||||||
"started_at": started_at,
|
"started_at": started_at,
|
||||||
"ended_at": ended_at,
|
"ended_at": ended_at,
|
||||||
|
"source_workorder_started_at": (
|
||||||
|
source_workorder_started_at
|
||||||
|
),
|
||||||
|
"source_workorder_ended_at": (
|
||||||
|
source_workorder_ended_at
|
||||||
|
),
|
||||||
"operator": operator,
|
"operator": operator,
|
||||||
"quantity": quantity,
|
"quantity": quantity,
|
||||||
"source_odoo_write_date": parse_odoo_datetime(
|
"source_odoo_write_date": parse_odoo_datetime(
|
||||||
@ -980,6 +1013,12 @@ def build_records(
|
|||||||
"product_uom_name": uom_name,
|
"product_uom_name": uom_name,
|
||||||
"started_at": started_at,
|
"started_at": started_at,
|
||||||
"ended_at": ended_at,
|
"ended_at": ended_at,
|
||||||
|
"source_workorder_started_at": (
|
||||||
|
source_workorder_started_at
|
||||||
|
),
|
||||||
|
"source_workorder_ended_at": (
|
||||||
|
source_workorder_ended_at
|
||||||
|
),
|
||||||
"session_status": session_status,
|
"session_status": session_status,
|
||||||
**operator,
|
**operator,
|
||||||
**quantity,
|
**quantity,
|
||||||
@ -1059,10 +1098,15 @@ def preserve_human_reviews(
|
|||||||
merged["quantity_status"] = "FINAL_POSTED"
|
merged["quantity_status"] = "FINAL_POSTED"
|
||||||
|
|
||||||
interval_complete = (
|
interval_complete = (
|
||||||
merged.get("started_at") is not None
|
(
|
||||||
and (
|
|
||||||
merged.get("session_status") == "OPEN"
|
merged.get("session_status") == "OPEN"
|
||||||
or merged.get("ended_at") is not None
|
and merged.get("started_at") is not None
|
||||||
|
and merged.get("ended_at") is None
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
merged.get("session_status") == "CLOSED"
|
||||||
|
and merged.get("started_at") is not None
|
||||||
|
and merged.get("ended_at") is not None
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
operator_final = merged.get(
|
operator_final = merged.get(
|
||||||
@ -1250,8 +1294,8 @@ def upsert_record(
|
|||||||
%(planned_quantity)s,
|
%(planned_quantity)s,
|
||||||
%(qty_producing)s,
|
%(qty_producing)s,
|
||||||
%(qty_produced)s,
|
%(qty_produced)s,
|
||||||
%(started_at)s,
|
%(source_workorder_started_at)s,
|
||||||
%(ended_at)s,
|
%(source_workorder_ended_at)s,
|
||||||
%(production_started_at)s,
|
%(production_started_at)s,
|
||||||
%(production_ended_at)s,
|
%(production_ended_at)s,
|
||||||
%(productivity_employee_ids)s,
|
%(productivity_employee_ids)s,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user