Record Civil Registration Data UK.fh_lua

--[[
@Title: Record Civil Registration Data (UK)
@Type: Source-driven data entry
@Subtype: "Civil Registration Index"
@Author: Calico Pie
@Version: 1.7
@Keywords: 
@LastUpdated: September 2022
@GH: #47 #48 #51 #76 #112 #119 #127 #130
@Description: Designed to work with the "Civil Registration Index" source template from the Essentials collection, this will add the related facts for birth, death or marriage to an existing fact. The required minimum Source information is Type, Region, Name and Date.
 
Supports the following regions: England and Wales, Scotland, Ireland, Northern Ireland (if you've created the region), Isle of Man (if you've created the region), and the Channel Islands (if you've created the region).

Note:  The data in the indices varies with time, so the DEA prompts for everything that was ever recorded in an index in the particular region; if you don't have a value for a particular field, leave it blank and it will be set to N/A. 
]]
fhInitialise(7)
fh = require("fhUtils")
fh.setIupDefaults()
local ptrHead = fhNewItemPtr()
local sPluginName = stringx.strip(fhGetContextInfo("CI_PLUGIN_NAME"))
local pForms, pCite, ptrHead
local buttons = { "OK", "Cancel" }
local buttons2 = { "OK", "Skip" }

function main()
	pCite = fh.loadPreparedCitation()

	pForms = loadForms()
	if not pCite.result then
		fh.getParam(sPluginName, pCite.err)
		return
	end
	-- Ensure all needed fields have been completed
	if not (pCite:checkRequired("EN-REGION", "EN-TYPE", "NM-NAME_RECORDED", "DT-DATE")) then
		fh.getParam(
			sPluginName,
			"Not all required fields set, please ensure you have entered Region, Type, Name and Date"
		)
		return
	end
	-- Validate the Type field is supported
	sType = pCite:getValue("EN-TYPE")
	sCountry = pCite:getValue("EN-REGION")
	if not pForms[sCountry] then
		fh.getParam(sPluginName, "Sorry " .. sCountry .. " not supported.")
		return
	end
	if not pForms[sCountry][sType] then
		fh.getParam(sPluginName, "Sorry " .. sType .. " not supported for " .. sCountry .. ".")
		return
	end
	pForms[sCountry][sType]["function"]()
end

function englandBirth(ptr)
	local fields = pForms[sCountry][sType]["fields"]
	local r = fh.getParam("Record Civil Registration Data", "", fields, buttons)
	local tUpdates = {}
	if r.ok then
		local ptrRecord = r.results["RECORD"]:Clone()

		sText = formatTextFromSource(
			pForms[sCountry][sType]["template_name"],
			pForms[sCountry][sType]["template_default"],
			pCite,
			r.results
		)
		createTextFromSource(pCite, sText, "citation")

		if r.results["ACTION"] == "create" then
			ptrRecord = createIndi(r.results["NAME"])
			local b = selectSex()
			local sSex = "U"
			if b.button_pressed == "Male" then
				sSex = "M"
			end
			if b.button_pressed == "Female" then
				sSex = "F"
			end
			local ptrSex = fhCreateItem("SEX", ptrRecord, true)
			fhSetValueAsText(ptrSex, sSex)

			table.insert(tUpdates, { ptrRecord:Clone(), "Created" })
			table.insert(tUpdates, { ptrSex:Clone(), "Added" })
		end
		local sAction = ""
		ptrBirth, sAction = fh.createUpdateFact(
			ptrRecord,
			"BIRT",
			"Birth",
			pCite:getValue("TX-REGISTRATION_DISTRICT"),
			pCite:getValue("DT-DATE")
		)
		if ptrBirth and ptrBirth:IsNotNull() then
			pCite:appendCitation(ptrBirth)
			table.insert(tUpdates, { ptrBirth:Clone(), sAction })
		end

		if r.results["CITE.INDI"] then
			pCite:appendCitation(ptrRecord)
			table.insert(tUpdates, { ptrRecord:Clone(), "Cited" })
		end
		fh.outputUpdatedFields(tUpdates, pCite)
	end
end

function englandDeath(ptr)
	local tUpdates = {}
	local fields = pForms[sCountry][sType]["fields"]
	r = fh.getParam("Record Civil Registration Death Data", "", fields, buttons)
	if r.ok then
		sText = formatTextFromSource(
			pForms[sCountry][sType]["template_name"],
			pForms[sCountry][sType]["template_default"],
			pCite,
			r.results
		)
		createTextFromSource(pCite, sText, "citation")

		local ptrRecord
		local sAction
		local ptrDeath, ptrBirth
		ptrRecord = r.results["RECORD"]:Clone()
		if r.results["ACTION"] == "create" then
			ptrRecord = createIndi(r.results["NAME"])
			local b = selectSex()
			local sSex = "U"
			if b.button_pressed == "Male" then
				sSex = "M"
			end
			if b.button_pressed == "Female" then
				sSex = "F"
			end
			local ptrSex = fhCreateItem("SEX", ptrRecord, true)
			fhSetValueAsText(ptrSex, sSex)

			table.insert(tUpdates, { fhGetItemPtr(ptrRecord, "~.NAME"), "Created" })
			table.insert(tUpdates, { ptrSex:Clone(), "Added" })
		end

		ptrDeath, sAction = fh.createUpdateFact(
			ptrRecord,
			"DEAT",
			"Death",
			pCite:getValue("TX-REGISTRATION_DISTRICT"),
			pCite:getValue("DT-DATE")
		)

		if ptrDeath and ptrDeath:IsNotNull() then
			pCite:appendCitation(ptrDeath)
			table.insert(tUpdates, { ptrDeath:Clone(), sAction })
		end
		local dtBirth = fhNewDate()
		if r.results["AGE"] then
			if ptrDeath and ptrDeath:IsNotNull() then
				-- Add Age to Death
				local ptrAge = fhCreateItem("AGE", ptrDeath, true)
				fhSetValueAsText(ptrAge, r.results["AGE"])
				dtBirth = fh.calcBirth(pCite:getValue("DT-DATE"), r.results["AGE"])
				table.insert(tUpdates, { ptrAge:Clone(), "Updated" })
			end
		end
		if fh.isSet(r.results["BIRT.DATE"]) then
			dtBirth = r.results["BIRT.DATE"]:Clone()
		end
		if r.results["CITE.INDI"] then
			pCite:appendCitation(ptrRecord)
			table.insert(tUpdates, { ptrRecord:Clone(), "Cited" })
		end
		ptrBirth, sAction = fh.createUpdateFact(ptrRecord, "BIRT", "Birth", "", dtBirth)
		if ptrBirth and ptrBirth:IsNotNull() then
			pCite:appendCitation(ptrBirth)
			table.insert(tUpdates, { ptrBirth:Clone(), sAction })
		end
		fh.outputUpdatedFields(tUpdates, pCite)
	end
end

function englandMarriage(ptr)
	local tUpdates = {}
	local ptrFam, ptrRecord, ptrSpouse, ptrH, ptrW = nil, nil, nil, nil, nil
	local r, r2, r3 = nil, nil, nil --for results of prompts
	local fields = pForms[sCountry][sType]["fields"]
	-- Set up child values
	if fields[3].value and fields[3].value:IsNotNull() then
		-- Set Family Values from Record
		fields[4].values, fields[4].prompts = updateFamList(fields[3].value)
		fields[4].value = fields[4].values[1]
	end

	r = fh.getParam("Record Civil Registration Marriage Data", "", fields, buttons)
	if r.ok then
		if r.results["ACTION"] == "create" then --set Individual record
			ptrRecord = fhNewItemPtr()
		else
			ptrRecord = r.results["RECORD"]:Clone()
		end
		if r.results["FAMILY"] then --set family record
			ptrFam = r.results["FAMILY"]:Clone()
		else
			ptrFam = fhNewItemPtr()
		end
		--Get any  missing information about the spouse
		if ptrFam:IsNull() then --a new family, so prompt for spouse information
			r2 = fh.getParam("Add Spouse Information", "", spousePromptForm(r.results["SPOUSE.NAME"]), buttons2)
			if r2.ok then
				r.results["SPOUSE.NAME"] = r2.results["SPOUSE.NAME"] --needed for Text from Source
			end
		else --existing family
			ptrH = fhGetItemPtr(ptrFam, "~.~SPOU[1]>")
			ptrW = fhGetItemPtr(ptrFam, "~.~SPOU[2]>")
			--is there a missing spouse?
			if ptrH:IsNull() or ptrW:IsNull() then --prompt for missing spouse information
				r2 = fh.getParam("Add Spouse Information", "", spousePromptForm(r.results["SPOUSE.NAME"]), buttons2)
				if r2.ok then
					r.results["SPOUSE.NAME"] = r2.results["SPOUSE.NAME"] --needed for Text from Source
				end
			else --existing couple; need to check what index has for spouse name
				local sname = utils.choose(ptrRecord == ptrH, fhGetDisplayText(ptrW), fhGetDisplayText(ptrH)) --first get the name of the Spouse of the principal
				r3 = fh.getParam("Spouse name as entered", "", spousenamePromptForm(sname), buttons2)
				if r3.ok then
					r.results["SPOUSE.NAME"] = r3.results["SPOUSE.NAME"] --needed for Text from Source
				else
					r.results["SPOUSE.NAME"] = sname --needed for Text from Source
				end
			end
		end

		sText = formatTextFromSource(
			pForms[sCountry][sType]["template_name"],
			pForms[sCountry][sType]["template_default"],
			pCite,
			r.results
		)
		createTextFromSource(pCite, sText, "citation") --should not change or add any data until this is done

		if ptrRecord:IsNull() then --create new Individual
			ptrRecord = createIndi(r.results["NAME"])
			local b = selectSex()
			local sSex = "U"
			if b.button_pressed == "Male" then
				sSex = "M"
			end
			if b.button_pressed == "Female" then
				sSex = "F"
			end
			local ptrSex = fhCreateItem("SEX", ptrRecord, true)
			fhSetValueAsText(ptrSex, sSex)
			table.insert(tUpdates, { ptrRecord:Clone(), "Created" })
			table.insert(tUpdates, { ptrSex, "Added" })
		end
		if r.results["CITE.INDI"] then
			pCite:appendCitation(ptrRecord)
		end
		if ptrFam:IsNull() then --create new Family
			ptrFam = createFamilyAsSpouse(ptrRecord)
			table.insert(tUpdates, { ptrFam:Clone(), "Created" })
		end
		if r.results["CITE.FAM"] then
			pCite:appendCitation(ptrFam)
		end
		-- Add Marriage
		local ptrMarriage, sAction = fh.createUpdateFact(
			ptrFam,
			"MARR",
			"Marriage",
			pCite:getValue("TX-REGISTRATION_DISTRICT"),
			pCite:getValue("DT-DATE")
		)
		if ptrMarriage and ptrMarriage:IsNotNull() then
			pCite:appendCitation(ptrMarriage)
			table.insert(tUpdates, { ptrMarriage:Clone(), sAction })
		end
		if r2 and r2.ok then --missing spouse has been specified
			if ptrFam:IsNotNull() then
				if r2.results["SPOUSE.ACTION"] == "create" then
					ptrSpouse = createIndi(r2.results["SPOUSE.NAME"])
					local sSex = fhGetItemText(ptrRecord, "~.SEX")
					if sSex == "Male" then
						sSex = "F"
					else
						sSex = "M"
					end
					local ptrSex = fhCreateItem("SEX", ptrSpouse, true)
					fhSetValueAsText(ptrSex, sSex)
					table.insert(tUpdates, { ptrSpouse:Clone(), "Created" })
					table.insert(tUpdates, { ptrSex:Clone(), "Added" })
				else
					ptrSpouse = r2.results["SPOUSE.RECORD"]:Clone()
				end
				if r2.results["SPOUSE.CITE"] then
					pCite:appendCitation(ptrSpouse)
				end
				-- Add Spouse to Family Record
				addFamilyAsSpouse(ptrSpouse, ptrFam)
				table.insert(tUpdates, { ptrFam:Clone(), "Updated" })
			end
		end
	end

	fh.outputUpdatedFields(tUpdates, pCite)
end

----------------------------------------------------------------- updateFamList (for Marriage)

function updateFamList(ptrRecord)
	if not ptrRecord or ptrRecord:IsNull() then
		return {}, {}
	end
	local famList = fh.familyList(ptrRecord, "FamilyAsSpouse")
	local prompts = {}
	local values = {}
	for i, f in ipairs(famList) do
		prompts[i] = f.label
		values[i] = f.ptr
	end
	return values, prompts
end

function createIndi(sName, bCite)
	local ptrRecord = fhCreateItem("INDI")
	local ptrName = fhCreateItem("NAME", ptrRecord)
	fhSetValueAsText(ptrName, sName)
	pCite:appendCitation(ptrName)
	return ptrRecord
end

function createFamilyAsChild(ptrIndi)
	-- Create Family As Child
	local ptrFam = fhCreateItem("FAM")
	local ptrFams = fhCreateItem("FAMC", ptrIndi)
	fhSetValueAsLink(ptrFams, ptrFam)
	pCite:appendCitation(ptrFams)
	return ptrFam
end

function createFamilyAsSpouse(ptrIndi)
	-- Create Family As Spouse
	local ptrFam = fhCreateItem("FAM")
	-- pCite:appendCitation(ptrFam)
	local ptrFams = fhCreateItem("FAMS", ptrIndi)
	fhSetValueAsLink(ptrFams, ptrFam)
	pCite:appendCitation(ptrFams)
	return ptrFam
end

function addFamilyAsChild(ptrIndi, ptrFam)
	-- Add Family As Child
	local ptrFamc = fhCreateItem("FAMC", ptrIndi)
	fhSetValueAsLink(ptrFamc, ptrFam)
	pCite:appendCitation(ptrFamc)
	return ptrFam
end

function addFamilyAsSpouse(ptrIndi, ptrFam)
	-- Add Family As Spouse
	local ptrFams = fhCreateItem("FAMS", ptrIndi)
	local bOK = fhSetValueAsLink(ptrFams, ptrFam)
	pCite:appendCitation(ptrFams)
	return ptrFam
end
--- ChildUpdate handler
-- @param value string - value of list box
-- @return boolean - true if value select

function selectOrCreate(value)
	if value == "select" then
		return false, 1
	else
		return true, 0
	end
end

function selectSex()
	local r = fh.getParam("Select Sex", "Select the Sex for the Principal", {}, { "Male", "Female" })
	return r
end

function createTextFromSource(pCite, sText, sType)
	local ptrParent, ptrTextFromSource
	local rt = fhNewRichText()
	local ptrParent = pCite.source
	rt:SetText(sText)
	if sType == "source" then
		ptrParent = pCite.source
	else
		ptrParent = fhGetItemPtr(pCite.ptr, "~.SOUR.DATA")
		if ptrParent:IsNull() then
			ptrParent = fhGetItemPtr(pCite.ptr, "~.SOUR")
			ptrParent = fhCreateItem("DATA", ptrParent)
		end
	end
	local ptrTextFromSource = fhGetItemPtr(ptrParent, "~.TEXT")
	if ptrTextFromSource:IsNull() then
		ptrTextFromSource = fhCreateItem("TEXT", ptrParent)
	end
	fhSetValueAsRichText(ptrTextFromSource, rt)
end

function formatTextFromSource(templatename, templateDefault, pCite, tOtherValues)
	-- Takes a template name for file, template default if no file found and two tables of fields to replace
	richTextReplace = fh.richTextReplace
	local tFields = tFields or {}
	local tOthervalues = tOtherValues or {}
	local sPlugin = stringx.strip(fhGetContextInfo("CI_PLUGIN_NAME"))
	local function mkdir(path)
		local sep = "\\"
		local pStr = ""
		for dir in path:gmatch("[^" .. sep .. "]+") do
			pStr = pStr .. dir .. sep
			lfs.mkdir(pStr)
		end
	end
	local sFolder = fhGetContextInfo("CI_APP_DATA_FOLDER") .. "\\AutoText for Plugins\\" .. sPlugin .. "\\"
	-- Check folder exists and create if not
	mkdir(sFolder)

	local f = sFolder .. templatename
	local d, e = file.read(f)
	if e then
		-- Create File from default
		d = templateDefault
		file.write(f, templateDefault)
	end
	-- Replace all citation fields in AutoText
	for k, v in pairs(pCite.fields) do
		local n = pCite:getDisplayValue(k) or ""
		d = richTextReplace(d, "{" .. k .. "}", n)
	end
	-- Replace all other fields in AutoText

	for k, v in pairs(tOtherValues) do
		local n
		if k == "BIRT.DATE" then
			n = v:GetDisplayText()
			if n == "" then
				n = "N/A"
			end
		else
			n = tostring(v) or ""
		end
		d = richTextReplace(d, "{" .. k .. "}", n)
	end
	d = d:gsub("{.-}", "")
	return d
end

function loadForms()
	local forms = {}
	local ptrDefault = fhGetCurrentPropertyBoxRecord()
	if ptrDefault:IsNotNull() and fhGetTag(ptrDefault) ~= "INDI" then
		ptrDefault:SetNull()
	end
	if fh.isSet(ptrDefault) then
		local ptrFamily = fhGetItemPtr(ptrDefault, "~.FAMS")
	end
	forms["England and Wales"] = {
		["Birth"] = {
			fields = {
				{
					tag = "NAME",
					label = "Name",
					type = "STRING",
					dr = "NAME",
					value = pCite:getDisplayValue("NM-NAME_RECORDED"),
					protect = true,
				},
				{
					tag = "ACTION",
					label = "Add principal as",
					type = "LIST",
					value = "select",
					values = { "create", "select" },
					prompts = { "Create New Record", "Use Existing Record" },
					child = "RECORD",
					childUpdate = function(...)
						return selectOrCreate(...)
					end,
				},

				{
					tag = "RECORD",
					label = "Record",
					type = "RECORD",
					dr = "PTR",
					value = ptrDefault:Clone(),
					minlength = 1,
				},
				{
					tag = "MAIDEN-NAME",
					label = "Mothers Maiden Name",
					type = "STRING",
					dr = "MAIDEN-NAME",
					value = "",
				},
				{
					tag = "CITE.INDI",
					label = "Add Citation to Individual Record",
					type = "BOOLEAN",
					value = true,
				},
			},
			["function"] = function(ptr)
				return englandBirth(ptr)
			end,
			["template_default"] = "{EN-REGION} Civil Index of {EN-TYPE}s\nDate: {DT-DATE} Name: {NM-NAME_RECORDED} Reference: {TX-REFERENCE} Registration District: {TX-REGISTRATION_DISTRICT} Mother's Maiden Name: {MAIDEN-NAME}",
			["template_name"] = "UK Birth Index.ftf",
		},
		["Marriage"] = {
			fields = {
				{
					tag = "NAME",
					label = "Name",
					type = "STRING",
					dr = "NAME",
					value = pCite:getDisplayValue("NM-NAME_RECORDED"),
					protect = true,
				},
				{
					tag = "ACTION",
					label = "Add principal as",
					type = "LIST",
					value = "select",
					values = { "create", "select" },
					prompts = { "Create New Record", "Use Existing Record" },
					child = "RECORD",
					childUpdate = function(...)
						return selectOrCreate(...)
					end,
				},
				{
					tag = "RECORD",
					label = "Record",
					type = "RECORD",
					dr = "PTR",
					value = ptrDefault:Clone(),
					minlength = 1,
					child = "FAMILY",
					childUpdate = function(...)
						return updateFamList(...)
					end,
				},
				{
					tag = "FAMILY",
					label = "Family",
					type = "LIST",
					values = {},
					prompts = {},
				},
				{
					tag = "CITE.INDI",
					label = "Add Citation to Individual Record",
					type = "BOOLEAN",
					value = true,
				},
				{
					tag = "CITE.FAM",
					label = "Add Citation to Family Record",
					type = "BOOLEAN",
					value = true,
				},
			},
			["function"] = function(ptr)
				return englandMarriage(ptr)
			end,
			["template_default"] = "{EN-REGION} Civil Index of {EN-TYPE}s\nDate: {DT-DATE} Name: {NM-NAME_RECORDED} Reference: {TX-REFERENCE} Registration District: {TX-REGISTRATION_DISTRICT} Spouse Name: {SPOUSE.NAME}",
			["template_name"] = "UK Marriage Index.ftf",
		},
		["Death"] = {
			fields = {
				{
					tag = "NAME",
					label = "Name",
					type = "STRING",
					dr = "NAME",
					value = pCite:getDisplayValue("NM-NAME_RECORDED"),
					protect = true,
				},
				{
					tag = "ACTION",
					label = "Add principal as",
					type = "LIST",
					value = "select",
					values = { "create", "select" },
					prompts = { "Create New Record", "Use Existing Record" },
					child = "RECORD",
					childUpdate = function(...)
						return selectOrCreate(...)
					end,
				},

				{
					tag = "RECORD",
					label = "Record",
					type = "RECORD",
					dr = "PTR",
					value = ptrDefault:Clone(),
					minlength = 1,
				},
				{
					tag = "AGE",
					label = "Age",
					type = "STRING",
					dr = "AGE",
					value = "",
					mask = "/d+[dwmy]",
				},
				{
					tag = "BIRT.DATE",
					label = "Birth Date",
					type = "DATE",
					dr = "BIRT.DATE",
					value = fhNewDate(),
				},
				{
					tag = "CITE.INDI",
					label = "Add Citation to Individual Record",
					type = "BOOLEAN",
					value = true,
				},
			},
			["function"] = function(ptr)
				return englandDeath(ptr)
			end,
			["template_default"] = "{EN-REGION} Civil Index of {EN-TYPE}s\nDate: {DT-DATE} Name: {NM-NAME_RECORDED} Reference: {TX-REFERENCE} Registration District: {TX-REGISTRATION_DISTRICT} Age: {AGE} Date of Birth: {BIRT.DATE}",
			["template_name"] = "UK Death Index.ftf",
		},
	}

	forms["Scotland"] = tablex.deepcopy(forms["England and Wales"])
	forms["Scotland"]["Death"]["template_name"] = "Scottish Death Index.ftf"
	forms["Scotland"]["Death"]["template_default"] =
		"{EN-REGION} Civil Index of {EN-TYPE}s\nDate: {DT-DATE} Name: {NM-NAME_RECORDED} Reference: {TX-REFERENCE} Registration District: {TX-REGISTRATION_DISTRICT} Age: {AGE} Date of Birth: {BIRT.DATE} Mother's Maiden Name: {MAIDEN-NAME}"
	table.insert(forms["Scotland"]["Death"]["fields"], 6, {
		tag = "MAIDEN-NAME",
		label = "Mothers Maiden Name",
		type = "STRING",
		dr = "MAIDEN-NAME",
		value = "",
	})
	forms["Ireland"] = tablex.deepcopy(forms["England and Wales"])
	forms["Northern Ireland"] = tablex.deepcopy(forms["England and Wales"])
	forms["Isle of Man"] = tablex.deepcopy(forms["England and Wales"])
	forms["Channel Islands"] = tablex.deepcopy(forms["England and Wales"])

	function forms.getIndex(fields, tag)
		for i, field in ipairs(fields) do
			if field.tag == tag then
				return i
			end
		end
		return false
	end
	return forms
end

function spousePromptForm(name)
	return {
		{
			tag = "SPOUSE.NAME",
			label = "Spouse Name",
			type = "STRING",
			value = "",
			minlength = 1,
		},
		{
			tag = "SPOUSE.ACTION",
			label = "Add Spouse as",
			type = "LIST",
			value = "select",
			values = { "create", "select" },
			prompts = { "Create New Record", "Use Existing Record" },
			child = "SPOUSE.RECORD",
			childUpdate = function(...)
				return selectOrCreate(...)
			end,
		},
		{
			tag = "SPOUSE.RECORD",
			label = "Spouse Record",
			type = "RECORD",
			dr = "PTR",
			value = fhNewItemPtr(),
			minlength = 1,
		},
		{
			tag = "SPOUSE.CITE",
			label = "Add Citation to Spouse Record",
			type = "BOOLEAN",
			value = true,
		},
	}
end

function spousenamePromptForm(name)
	return {
		{
			tag = "SPOUSE.NAME",
			label = "Spouse Name",
			type = "STRING",
			value = name,
			minlength = 1,
		},
	}
end
----------------------------------------------------- Call Main
main()

Source:Record-Civil-Registration-Data-UK-7.fh_lua