From d62269c790922ee159626b45a66c24b214955e03 Mon Sep 17 00:00:00 2001 From: Remco van 't Veer Date: Tue, 11 Feb 2025 13:11:33 +0100 Subject: [PATCH] List DCSA events in ERP --- resources/i18n.yml | 19 +++++++++ resources/public/assets/base.css | 13 +++++- resources/public/assets/erp.css | 71 ++++++++++++++++++++++++++++++++ src/dil_demo/erp.clj | 26 ++---------- src/dil_demo/erp/web.clj | 22 +++++++++- src/dil_demo/web_utils.clj | 39 ++++++++++++++++++ 6 files changed, 165 insertions(+), 25 deletions(-) diff --git a/resources/i18n.yml b/resources/i18n.yml index b8c063b..40a816f 100644 --- a/resources/i18n.yml +++ b/resources/i18n.yml @@ -104,6 +104,7 @@ nl: erp/title/new: "Details" erp/title/publish: "Verzenden" erp/title/published: "Order %{ref} verstuurd" + erp/title/dcsa-events: "DCSA events" warning/already-published: "Let op! Deze opdracht is al verzonden!" tms/button/assign: "Toewijzen" @@ -175,6 +176,14 @@ nl: events/title/event: "Notificatie" events/no-found: "Unknown event" + dcsa-event/type/EQUIPMENT/GTIN: "Gate-in" + dcsa-event/type/EQUIPMENT/LOAD: "Geladen" + dcsa-event/type/TRANSPORT/DEPA: "Vertrokken" + dcsa-event/date-time: "Tijdstip" + dcsa-event/equipment-reference: "Container nr." + dcsa-event/port-visit-ref: "Havenbezoek ref." + dcsa-event/button/raw: "Event data" + explanation: "Uitleg" explanation/event: "Bekijk event" explanation/events/access-policy: "Toevoegen policy toegang event broker" @@ -305,6 +314,7 @@ en: erp/title/new: "Details" erp/title/publish: "Submit" erp/title/published: "Order %{ref} submitted" + erp/title/dcsa-events: "DCSA events" warning/already-published: "Attention! This order has already been sent!" tms/button/assign: "Assign" @@ -376,6 +386,15 @@ en: events/title/event: "Notification" events/no-found: "Unknown event" + dcsa-event/type/EQUIPMENT/GTIN: "Gate-in" + dcsa-event/type/EQUIPMENT/LOAD: "Loaded" + dcsa-event/type/TRANSPORT/DEPA: "Departed" + dcsa-event/date-time: "Time" + dcsa-event/equipment-reference: "Container nr." + dcsa-event/port-visit-ref: "Port visit ref." + dcsa-event/button/raw: "Event data" + + explanation: "Explanation" explanation/event: "View event" explanation/events/access-policy: "Add access policy to event broker" diff --git a/resources/public/assets/base.css b/resources/public/assets/base.css index ae0f352..51ce7a9 100644 --- a/resources/public/assets/base.css +++ b/resources/public/assets/base.css @@ -933,7 +933,6 @@ dialog#drawer-dialog { max-height: unset; margin: unset; } - dialog#drawer-dialog main.container { box-sizing: border-box; height: calc(100% - var(--header-height)); @@ -993,6 +992,18 @@ dialog .dialog-close { } } +@keyframes pulsate { + 0% { + box-shadow: var(--color-orange) 0 0 0; + } + 50% { + box-shadow: var(--color-orange) 0 0 1em; + } + 100% { + box-shadow: var(--color-orange) 0 0 0; + } +} + dialog .busy { display: block; } diff --git a/resources/public/assets/erp.css b/resources/public/assets/erp.css index 602b405..f1fa777 100644 --- a/resources/public/assets/erp.css +++ b/resources/public/assets/erp.css @@ -11,3 +11,74 @@ --site-color: var(--erp-color); --site-other-color: var(--erp-other-color); } + +dialog ol.consignment-dcsa-events { + padding: 1rem 2rem 1rem 3rem; +} +ol.consignment-dcsa-events { + display: flex; + flex-direction: column-reverse; +} +ol.consignment-dcsa-events li { + flex: 0 0 auto; + margin-bottom: 2rem; +} +ol.consignment-dcsa-events dl { + margin-bottom: 1rem; +} + +table.list :is(th, td):is(.status) { + width: 9rem; +} + +.dcsa-event { + position: relative; +} +.dcsa-event:before { + font-family: 'icons'; + font-size: 2.5rem; + display: inline-block; + width: 3rem; + height: 3rem; + line-height: 3rem; + position: absolute; + top: 1rem; + right: 0; + background: var(--color-tinted-bg); + border-radius: 1rem; + padding: .5rem; + box-size: border-box; + text-align: center; +} + +.dcsa-event-EQUIPMENT-GTIN:before { + content: var(--icon-input) +} + +.dcsa-event-EQUIPMENT-LOAD:before { + content: var(--icon-stacks) +} + +.dcsa-event-TRANSPORT-DEPA:before { + content: var(--icon-directions_boat) +} + +a.info { + display: inline-block; + width: 1.5rem; + height: 1.5rem; + line-height: 1.5rem; + border-radius: 1.5rem; + margin: 0 .5rem; + + font-family: serif; + font-style: italic; + font-weight: bold; + text-align: center; + text-decoration: none; + + background: var(--color-blue); + color: var(--color-white); + + animation: pulsate 2s linear infinite; +} diff --git a/src/dil_demo/erp.clj b/src/dil_demo/erp.clj index 6c9fbcb..c901007 100644 --- a/src/dil_demo/erp.clj +++ b/src/dil_demo/erp.clj @@ -9,7 +9,6 @@ (:require [babashka.http-client :as http] [clojure.core.match :refer [match]] [clojure.tools.logging :as log] - [dil-demo.dcsa-events-connector :as dcsa-events-connector] [dil-demo.erp.web :as erp.web] [dil-demo.events :as events] [dil-demo.i18n :refer [t]] @@ -113,29 +112,12 @@ (filter #(= ref (:ref %)) $) (first $))) -(defmulti handle-dcsa-event - (fn [_res _store [_order-ref event]] - (dcsa-events-connector/event-type event))) - -(defmethod handle-dcsa-event ["EQUIPMENT" "GTIN"] - [res store [order-ref _event]] - (if-let [consignment (get-consigment store order-ref)] - (update res :store/commands (fnil conj []) - [:put! :consignments (assoc consignment :status "TODO-GTIN")]) - res)) - -(defmethod handle-dcsa-event ["EQUIPMENT" "LOAD"] - [res store [order-ref _event]] - (if-let [consignment (get-consigment store order-ref)] - (update res :store/commands (fnil conj []) - [:put! :consignments (assoc consignment :status "TODO-LOAD")]) - res)) - -(defmethod handle-dcsa-event ["TRANSPORT" "DEPA"] - [res store [order-ref _event]] +(defn handle-dcsa-event + [res store [order-ref event]] (if-let [consignment (get-consigment store order-ref)] (update res :store/commands (fnil conj []) - [:put! :consignments (assoc consignment :status "TODO-DEPA")]) + [:put! :consignments (update consignment :dcsa-events (fnil conj []) + event)]) res)) (defn wrap-incoming-portbase-event [app] diff --git a/src/dil_demo/erp/web.clj b/src/dil_demo/erp/web.clj index 7feeec7..43ca82d 100644 --- a/src/dil_demo/erp/web.clj +++ b/src/dil_demo/erp/web.clj @@ -41,7 +41,7 @@ (when-not (seq consignments) [:tr.empty [:td {:colspan 999} (t "empty")]]) - (for [{:keys [id ref goods load unload carrier status]} consignments] + (for [{:keys [id ref goods load unload carrier status dcsa-events]} consignments] [:tr.fx-clickable [:td.ref [:a {:href (str "consignment-" id) @@ -54,7 +54,11 @@ [:td.load-date [:span (:date load)]] [:td.unload-location [:span (-> unload :location-name)]] [:td.unload-date [:span (:date unload)]] - [:td.status (w/status-span status)] + [:td.status + (w/status-span status) + (when (seq dcsa-events) + [:a.info {:href (str "consignment-dcsa-events-" id) + :fx-dialog "#modal-dialog"} "i"])] [:td.publish (if (= otm/status-draft status) [:a.button.primary.publish @@ -64,6 +68,11 @@ (t "erp/button/publish")] [:span.carrier (-> carrier :eori eori->name)])]])]]]]) +(defn consignment-dcsa-events [{:keys [dcsa-events]}] + [:ol.consignment-dcsa-events + (for [event dcsa-events] + [:li (w/render-dcsa-event event)])]) + (defn editable? [{:keys [status]}] (or (nil? status) (= otm/status-draft status))) @@ -278,8 +287,17 @@ (-> "." (redirect :see-other) (update-consignment consignment) + (assoc :flash {:success (t "erp/flash/create-success" {:ref ref})})))) + (GET "/consignment-dcsa-events-:id" {:keys [flash store] + {:keys [id]} :params} + (when-let [{:keys [ref] :as consignment} (get-consignment store id)] + (render (t "erp/title/dcsa-events" {:ref ref}) + (consignment-dcsa-events consignment) + flash + :html-class "details"))) + (GET "/consignment-:id" {:keys [flash master-data store] {:keys [id]} :params} (when-let [{:keys [ref] :as consignment} (get-consignment store id)] diff --git a/src/dil_demo/web_utils.clj b/src/dil_demo/web_utils.clj index 8877364..bca29c0 100644 --- a/src/dil_demo/web_utils.clj +++ b/src/dil_demo/web_utils.clj @@ -8,10 +8,13 @@ (ns dil-demo.web-utils (:require [clojure.data.json :as json] [clojure.string :as string] + [dil-demo.dcsa-events-connector :as dcsa-events-connector] [dil-demo.i18n :as i18n :refer [t]] [hiccup2.core :as hiccup] [ring.util.response :as response]) (:import (java.text SimpleDateFormat) + (java.time Instant) + (java.time.format DateTimeFormatter) (java.util UUID))) (defn dummy-link [title] @@ -117,6 +120,13 @@ (defn format-date [date] (.format (SimpleDateFormat. "yyyy-MM-dd") date)) +(def tstamp-formatter + (.withZone (java.time.format.DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm:ss") + (java.time.ZoneId/systemDefault))) + +(defn format-tstamp [tstamp] + (.format tstamp-formatter tstamp)) + (defn or-em-dash [val] (if (string/blank? val) "—" @@ -182,6 +192,35 @@ :else (json/write-str val :escape-slash false))))) +(defn dcsa-event-tstamp [{{date-time "eventDateTime"} "payload"}] + (Instant/from (.parse DateTimeFormatter/ISO_INSTANT date-time))) + +(defn render-dcsa-event + [{{equipment-reference "equipmentReference" + {port-visit-ref "portVisitReference"} "transportCall"} "payload" + :as event}] + [:div.dcsa-event + {:class (string/join "-" (concat ["dcsa-event"] + (dcsa-events-connector/event-type event)))} + [:h3 (t (string/join "/" (concat ["dcsa-event/type"] + (dcsa-events-connector/event-type event))))] + [:dl.meta + [:div + [:dt (t "dcsa-event/date-time")] + [:dd (-> event (dcsa-event-tstamp) (format-tstamp))]] + (when equipment-reference + [:div + [:dt (t "dcsa-event/equipment-reference")] + [:dd [:code equipment-reference]]]) + (when port-visit-ref + [:div + [:dt (t "dcsa-event/port-visit-ref")] + [:dd [:code port-visit-ref]]])] + + [:details + [:summary (t "dcsa-event/button/raw")] + [:pre.payload (to-json event)]]]) + (defn otm-to-json [val] (to-json val :key-fn (comp camelize name)))