Record Burial Data UK.fh_lua

--[[
@Title: Record Burial Data (UK)
@Type: Source-driven Data Entry
@Subtype: "Church Register"
@Author: Calico Pie
@Version: 1.5
@gh: #54 #76 #112 #119
@Keywords: 
@LastUpdated: November 2021 
@Description: Based on the Church of England Burial Register, creates burial and residence facts, and optionally a birth and/or death fact.
]]

fhInitialise(7)
fh = require 'fhUtils'
fh.setIupDefaults()

-- Local Globals
local form, templateName, templateDefault
local iForm = 1
local sPluginName = fhGetContextInfo 'CI_PLUGIN_NAME'
local s = {} -- Autofill table
local pCite -- Citation Object
local tUpdatedFields = {} -- Added and updated fields
local cells = {} -- Main Dialog sections

--- Builds dialog and drives process.
function main()
	
	-- ------------------------------------------------------------Load citation and associated fields
	pCite = fh.loadPreparedCitation()

	if not pCite.result then
		fh.getParam(sPluginName, pCite.err)
		return
	end

	-- Church Register uses EN-EVENT_TYPE but Civil Certificate uses EN-TYPE so copy values to Type field in Citation fields.
	if not pCite.fields['EN-TYPE'] then
		pCite.fields['EN-TYPE'] = pCite.fields['EN-EVENT_TYPE']
	end

	if pCite:getValue 'EN-TYPE' ~= 'Burial' then
		-- and pCite:getValue('EN-TYPE') ~= 'Cremation' then
		fh.getParam(sPluginName, 'Only Burial is supported by this plugin. ')
		return
	end

	-- Ensure all needed fields have been completed
	if
		not (
			pCite:checkRequired(
				'EN-TYPE',
				'NM-PRINCIPAL',
				'DT-DATE',
				'TX-CHURCH',
				'PL-LOCATION'
			)
		)
	then
		fh.getParam(
			sPluginName,
			'Not all required fields are set,\nplease ensure you have entered type, date, church, location and principal. '
		)
		return
	end

	-- Get form and load values from Citation
	local masterForms = loadMasterForms()
	form = tablex.deepcopy(masterForms[iForm]['sections'])
	templateName = masterForms[iForm]['templateName']
	templateDefault = masterForms[iForm]['templateDefault']

	form.principal.fields[1].value = pCite:getValue 'NM-PRINCIPAL'

	dtEventDateString = pCite:getDisplayValue 'DT-DATE'
	dtEventDate = pCite:getValue 'DT-DATE'

	-- ------------------------------------------------------------ Get currently selected person
	local ptrPrincipal = fh.getCurrentIndividual()
	if ptrPrincipal:IsNotNull() then
		form.principal.fields[field(form.principal.fields, 'RECORD')].value =
			ptrPrincipal:Clone()
		form.principal.fields[field(form.principal.fields, 'ACTION')].value =
			'select'
		form.principal.fields[field(form.principal.fields, 'SEX')].value =
			fhGetItemText(
				ptrPrincipal,
				'~.SEX'
			)
		form.principal.fields[field(form.principal.fields, 'RECORD')].protect =
			false
	end
	-- ------------------------------------------------------------ Create Dialog
	for k, v in pairs(form) do
		grid = iup.gridbox {
			numdiv = 2,
			cgaplin = '1',
			cgapcol = '1',
			margin = '4x4',
			expand = 'YES',
		}
		for l, f in ipairs(v.fields) do
			if f.label then
				local label = '      '
				local value = '                                       '
				if f.gridDisplay or v.gridDisplay then
					label = f.label .. ':'
					value = fh.getParamValueForDisplay(f, true)
				end
				f.gridLabel = iup.label { title = label, size = '80', expand = 'NO' }
				iup.Append(grid, f.gridLabel)
				f.iupgrid = iup.label { title = value, expand = 'YES' }
				iup.Append(grid, f.iupgrid)
			end
		end
		v.button = iup.button { title = 'Edit', active = 'NO' }
		if v.active then
			v.button.active = 'YES'
		end
		v.button.action = function()
			doEdit(k, v.title, v.seq)
		end
		cells[v.seq] = iup.vbox {
			EXPANDCHILDREN = 'YES',
			EXPAND = 'YES',
			iup.frame {
				title = v.title,
				EXPANDCHILDREN = 'YES',
				EXPAND = 'YES',
				iup.vbox {
					EXPANDCHILDREN = 'YES',
					EXPAND = 'YES',
					v.button,
					grid,
				},
			},
		}
	end

	local placeList = fh.createPlaceList()
	s['PLAC'] = placeList
	s['OCCU'] = fhGetDataList 'OCCUPATIONS'
	local addressList = {}

	txtMessage = iup.label { title = ' ' }

	btnOk = iup.button { title = 'OK', padding = '20x4', expand = 'HORIZONTAL' }

	function btnOk.action()
		if form.principal.valid then
			local sVenue = pCite:getValue 'TX-CHURCH'
			if not sVenue then
				sVenue = pCite:getValue 'AD-ADDRESS'
			end
			processDeath(pCite:getValue 'PL-LOCATION', sVenue)
			return iup.CLOSE
		else
			fhMessageBox 'Information has not been entered on Principal'
		end
	end
	btnCancel =
		iup.button { title = 'Cancel', padding = '4x4', expand = 'HORIZONTAL' }

	function btnCancel.action()
		return iup.CLOSE
	end

	local sTitle = 'Death Entry: ' .. fhGetDisplayText(pCite.source)
	dlg = iup.dialog {
		title = sTitle,
		minsize = '1000',
		iup.vbox {
			EXPANDCHILDREN = 'YES',
			EXPAND = 'YES',

			iup.gridbox {
				numdiv = 2,
				cgaplin = '1',
				cgapcol = '1',
				margin = '4x4',
				expand = 'YES',
				EXPANDCHILDREN = 'YES',
				table.unpack(cells),
			},
			iup.hbox {
				margin = '4x4',
				expand = 'NO',
				btnOk,
				btnCancel,
				fh.helpButton 'record-Death-data-uk',
				txtMessage,
			},
		},
	}
	iup.SetAttribute(dlg, 'NATIVEPARENT', fhGetContextInfo 'CI_PARENT_HWND') -- Set the parent window handle
	iup.SetHandle('main', dlg)
	iup.SetGlobal('PARENTDIALOG', 'main')

	iup.Popup(dlg)
	iup.Destroy(dlg)
end
-- --------------------------------------------------------------- doEdit
--- Called from the Edit buttons to update the sections
-- @param sType string, section name from form table
-- @param sTitle string Name of dialog
-- @param seq  number, sequence number of section
function doEdit(sType, sTitle, seq)
	local fields = form[sType].fields
	if sType == 'principal' then
		local oldRecord =
			form[sType]['fields'][field(fields, 'RECORD')].value:Clone()
		local r = fh.getParam(
			'Enter ' .. sTitle,
			nil,
			fields,
			{ 'OK', 'Cancel', fh.helpButton 'record-burial-uk' },
			s
		)
		if r.ok then
			form[sType].valid = true
			-- enable all Edit buttons
			for k, v in pairs(form) do
				v.button.active = 'YES'
			end
		end
	else
		local r = fh.getParam(
			'Enter ' .. sTitle,
			nil,
			fields,
			{ 'OK', 'Cancel' },
			s
		)
		if r.ok then
			form[sType].valid = true
			form[sType].gridDisplay = true
		end
	end
	updateGrid()
end

-- --------------------------------------------------------------- updateGrid
--- Update All Grid Fields from table
function updateGrid()
	for sType, sSection in pairs(form) do
		for l, f in ipairs(sSection.fields) do
			local label = ' '
			local value = ' '
			if f.gridDisplay or form[sType].gridDisplay then
				label = f.label
				value = fh.getParamValueForDisplay(f, true)
			end
			if f.gridLabel then
				f.gridLabel.title = label
			end
			if f.iupgrid then
				f.iupgrid.title = value
			end
		end
	end
end

function processDeath(sPlace, sAddress, sCelebrant)
	local fields = {}
	local data = {}
	for k, s in pairs(form) do
		data[k] = {}
		for i, f in ipairs(s.fields) do
			data[k][f.tag] = { value = f.value, type = f.type, dr = f.df }
		end
	end

	-- Create needed Individual Records
	for _, f in pairs(data) do
		if f.ACTION and f.ACTION.value == 'create' then
			local sex
			if f.SEX and f.SEX.value then
				sex = f.SEX.value
			end
			local ptr = fh.createIndi(f.NAME.value, sex)
			addCitation(ptr, 'Created')
			local ptrName = fhGetItemPtr(ptr, '~.NAME')
			addCitation(ptrName, 'Cited')
			f.RECORD.value:MoveTo(ptr)
		end
	end

	local eventDate = pCite:getValue 'DT-DATE'
	local sPlace = pCite:getValue 'PL-LOCATION'
	local aAddress = pCite:getValue 'TX-CHURCH'

	local dtDeath = data.principal.DATE.value
	if dtDeath:IsNull() then
		dtDeath = eventDate:Clone()
	end

	-- Create / Update Burial/Cremation Event
	local sType = 'BURI'
	local sFactLabel = 'Burial'
	if pCite:getValue 'EN-TYPE' == 'Cremation' then
		sType = 'CREM'
		sFactLabel = 'Cremation'
	end
	local ptrBurial = fhGetItemPtr(data.principal.RECORD.value, '~.' .. sType)
	local sAction = 'Added'
	if ptrBurial:IsNotNull() then
		ptrBurial, sAction = fh.createUpdateFact(
			data.principal.RECORD.value,
			sType,
			sFactLabel,
			sPlace,
			eventDate,
			sAddress
		)
	else
		ptrBurial = fh.createFact(
			data.principal.RECORD.value,
			sType,
			sPlace,
			eventDate,
			sAddress
		)
	end
	if ptrBurial then
		addCitation(ptrBurial, sAction)
	end

	-- Create / Update Death Event

	local ptrDeath = fhGetItemPtr(data.principal.RECORD.value, '~.DEAT')
	sAction = 'Added'
	if ptrDeath:IsNotNull() then
		ptrDeath, sAction = fh.createUpdateFact(
			data.principal.RECORD.value,
			'DEAT',
			'Death',
			nil,
			dtDeath
		)
	else
		ptrDeath = fh.createFact(data.principal.RECORD.value, 'DEAT', nil, dtDeath)
	end
	if ptrDeath then
		addCitation(ptrDeath, sAction)
	end

	-- Create Update Birth Event
	local dtBirth = fhNewDate()
	sAction = 'Added'
	if fh.isSet(data.principal.AGE.value) then
		dtBirth = fh.calcBirth(eventDate, data.principal.AGE.value)
	end
	if not (dtBirth:IsNull()) then
		local ptrBirth = fhGetItemPtr(data.principal.RECORD.value, '~.BIRT')
		if ptrBirth:IsNotNull() then
			ptrBirth, sAction = fh.createUpdateFact(
				data.principal.RECORD.value,
				'BIRT',
				'Birth',
				nil,
				dtBirth
			)
		else
			ptrBirth = fh.createFact(
				data.principal.RECORD.value,
				'BIRT',
				nil,
				dtBirth
			)
		end
		if ptrBirth then
			addCitation(ptrBirth, sAction)
		end
	end

	-- Record Occupation and Residence with death date if available else use Event Date.

	if data.principal.OCCU and data.principal.OCCU.value:len() > 0 then
		local ptrFact
		ptrFact, sAction = fh.createFact(
			data.principal.RECORD.value,
			'OCCU',
			nil,
			dtDeath,
			nil,
			data.principal.OCCU.value
		)
		if ptrFact then
			addCitation(ptrFact, 'Added')
		end
	end
	if data.principal.PLAC and data.principal.PLAC.value:len() > 0 then
		local ptrFact, sAction = fh.createFact(
			data.principal.RECORD.value,
			'RESI',
			data.principal.PLAC.value,
			dtDeath,
			data.principal.ADDR.value
		)
		if ptrFact then
			addCitation(ptrFact, 'Added')
		end
	end

	local fields = {}
	for k, s in pairs(form) do
		for i, f in ipairs(s.fields) do
			local inx = string.upper(k .. '.' .. tostring(f.tag))
			fields[inx] = fh.getParamValueForDisplay(f)

			if f.tag == 'NAME' and data[k].RECORD then
				local ptr = data[k].RECORD.value
				if ptr:IsNotNull() then
					-- Add record link
					fields[inx] = fh.richTextRecordLink(ptr, fields[inx])
				end
			end
		end
	end
	if not (pCite:checkRequired 'AD-ADDRESS') then
		fields['AD-ADDRESS'] = ''
	end
	sText = fh.formatTextFromSource(templateName, templateDefault, pCite, fields)
	fh.createTextFromSource(pCite, sText, 'source')

	fh.outputUpdatedFields(tUpdatedFields, pCite)
end

function addCitation(ptr, action)
	local action = action or ''
	table.insert(tUpdatedFields, { ptr:Clone(), action })
	pCite:appendCitation(ptr)
end

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

function field(fields, tag)
	local i
	for i, v in ipairs(fields) do
		if tag == v.tag then
			return i
		end
	end
end

function checksex(value)
	if value:IsNotNull() then
		local sex = fhGetItemText(value, '~.SEX')
		return { sex }
	else
		return { 'Male', 'Female', 'Unknown' }
	end
end

-- --------------------------------------------------------------- address
--- getParam helper returns list of addresses for place
-- @param place string, place name
-- @return table of matching addresses
function address(place)
	local addr = fh.createAddressList(place)
	return addr
end

function loadMasterForms()
	ptrFamily = fhNewItemPtr()
	masterForms = {}
	masterForms[1] = {
		templateName = 'Burial (UK).ftf',
		templateDefault = [[{TX-CHURCH} Parish Register, {PL-LOCATION} - {EN-TYPE} {NM-PRINCIPAL} {DT-DATE}

{TX-CHURCH}


 Date | Name and Surname | Age | Date of Death | Occupation | Residence | Signature of Celebrant 
 {DT-DATE}| {PRINCIPAL.NAME} | {PRINCIPAL.AGE}|{PRINCIPAL.DATE}|{PRINCIPAL.OCCU}|{PRINCIPAL.ADDR}
{PRINCIPAL.PLAC}| {PRINCIPAL.REG} 

]],
		sections = {
			principal = {
				fields = {
					{
						tag = 'NAME',
						label = 'Name',
						type = 'STRING',
						dr = 'NAME',
						value = pCite:getValue 'NM-PRINCIPAL',
						minlength = 1,
						protect = true,
					},
					{
						tag = 'ACTION',
						label = 'Add deceased as',
						type = 'LIST',
						value = 'create',
						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 = fhNewItemPtr(),
						protect = true,
						child = 'SEX',
						childUpdate = checksex,
					},
					{
						tag = 'SEX',
						type = 'LIST',
						label = 'Sex',
						dr = 'SEX',
						value = 'Male',
						values = { 'Male', 'Female', 'Unknown' },
					},
					{ tag = 'OCCU', dr = 'OCCU', type = 'STRING', label = 'Occupation', value = '' },
					{
						tag = 'PLAC',
						type = 'STRING',
						label = 'Residence Place',
						value = '',
						child = 'ADDR',
						childUpdate = address,
					},
					{ tag = 'ADDR', type = 'STRING', label = 'Residence Address', value = '' },
					{
						tag = 'AGE',
						type = 'STRING',
						label = 'Age',
						value = '',
						mask = '(/d+[dwmyf]|full)',
						minlength = 0,
					},
					{ tag = 'DATE', type = 'DATE', label = 'Date of Death', value = fhNewDate() },
					{ tag = 'REG', type = 'STRING', label = 'Recorded by', value = '' },
				},
				title = 'Burial Details',
				label = 'principal',
				seq = 1,
				active = true,
				gridDisplay = true,
			},
		},
	}
	return masterForms
end
-------------------------------------------------------------------------------------------------------- Start Plugin
main()

Source:Record-Burial-Data-UK-5.fh_lua