Letâs start with a very simplified version of your example to illustrate the problem:
module Main where
import Daml.Script
data CustomsInfo = CustomsInfo
  deriving (Eq, Show)
template ShipmentReceived
  with
    p : Party
    customsInfo : Optional CustomsInfo
 where
    signatory p
    controller p can
      AddCustomsInfo : ContractId ShipmentReceived
        with
          customsInfoNew : CustomsInfo
        do
          let
            this.customsInfo = customsInfoNew
          create ShipmentReceived with ..
setup : Script ()
setup = do
  p <- allocatePartyWithHint "P" $ PartyIdHint with partyIdHint = "P"
  cid <- submit p (createCmd ShipmentReceived with p = p, customsInfo = None)
  shipments <- query @ShipmentReceived p
  debug shipments
  cid <- submit p (exerciseCmd cid (AddCustomsInfo CustomsInfo))
  shipments <- query @ShipmentReceived p
  debug shipments
  pure ()
As you said, this doesnât work. We get the following output when running daml script:
[DA.Internal.Prelude:540]: [(<contract-id>,ShipmentReceived {p = 'P', customsInfo = None})]
[DA.Internal.Prelude:540]: [(<contract-id>,ShipmentReceived {p = 'P', customsInfo = None})]
Now what is going wrong here?
The crucial point is the following piece of code
          let
            this.customsInfo = customsInfoNew
          create ShipmentReceived with ..
If you remove a bit of syntactic sugar this is equivalent to
          let
            this.customsInfo = customsInfoNew
          create ShipmentReceived with p = p, customsInfo = customsInfo
Now the crucial questions is what does the customsInfo at the very end refer to.
To understand that we have to understand what the following line does.
let this.customsInfo = customsInfoNew
Remember that DAML is an immutable language. You cannot simply mutate a field in a template. This line isnât mutating the customsInfo field so that customsInfo changes its meaning. customsInfo at the end still refers to the old value which is why you see the unchanged value.
To fix your code you have to actually change the value, e.g.,
createCmd ShipmentReceived with p = p, customsInfo = Some customsInfoNew
or if you want to use a let and .. you can use the following
-- Note that this does not mutate customsInfo. It defines a new variable of this name that hides the one from your template.
let customsInfo = Some customsInfoNew
createCmd ShipmentReceived with ..
If you now rebuild and rerun your script you will see the expected output:
[DA.Internal.Prelude:540]: [(<contract-id>,ShipmentReceived {p = 'P', customsInfo = None})]
[DA.Internal.Prelude:540]: [(<contract-id>,ShipmentReceived {p = 'P', customsInfo = Some CustomsInfo})]
Now there is one remaining piece of the puzzle. What does let this.customsInfo = customsInfoNew actually do? This is rather confusing: It defines an infix operator called . which accepts two arguments this and customsInfo and will return customsInfoNew. So you could call "hello" . "world" afterwards and it will give you back customsInfoNew.