#INCLUDE wconnect.h

SET PROCEDURE TO wwBusinessObject ADDITIVE
SET PROCEDURE TO wwUtils ADDITIVE
SET PROCEDURE TO wwCollections ADDITIVE

*************************************************************
DEFINE CLASS wwBusinessObject AS CUSTOM
*************************************************************
*: Author: Rick Strahl
*:         (c) West Wind Technologies, 2018
*: Contact: http://www.west-wind.com
*: Created: 11/09/2018
*************************************************************


*** Location of the data
cDataPath = ""

*** Filename of the master file of this business object.
cFilename = ""

*** The Alias of the master file.
cAlias = ""

*** Table used to generate new PK Ids.
cIdTable = "wws_id"

*** Data member that receives all of the field names as property values.
oData = .NULL.

*** Any error messages that occurred.
cErrorMsg = ""

*** Error flag for the previous operation. Note this property must be managed by the developer for custom methods.
lError = .F.


*** Determines whether the data member is in edit or  new mode. 1-Edit, 2 - New
nUpdateMode = 1

*** Determines how data requests are returned. 0 - VFP Cursor, 2 - XML, 4 - ADO rs
nResultMode = 0

*** Requests that return a Fox cursor will use this name for the cursor name.
cSqlCursor = "TQuery"

*** Methods returning XML will return the XML in this property.
cResultXml = ""

*** SQL String for a query
cSql = ""

*** Variable result property. Used at this point only for ADO recordsets returned as a result from queries.
vResult = ""

*** SQL ConnectString if DataMode=2
cConnectString = ""

*** wwSQL object that handles data connection if nDatamode = 2. All SQL commands run through this object for remote data.
oSql = .NULL.

*** Data access mode for the business object. 0 - Fox Data native, 2 - ODBC (SQL Server)
nDataMode = 0

*** Property that holds fields that are skipped for Insert and Update statements built on the fly. Use this if you pull extra fields that aren't part of the base cursor used in Save operations.
cSkipfieldsForUpdates = ""

*** The field used as the primary key in the table. This field must be an integer or numeric field.
cPkField = "PK"

*** Flag that determines whether a Valid
lValidateOnSave = .F.

*** wwHTTPSQL object reference used for nDataMode=4 to do data access on the Web Server.
oHttpSql = .NULL.

*** Full Server URL used for HTTP communication.
cServerUrl = ""

*** The path where the ID table for auto-generation of PKs is found. Blank by default which assumes the path is in the Fox path.
cIdDataPath = ""

*** Location of the startup directory where the COM server is loaded. Only set if SetCOMEnvironment is called.
cAppStartPath = ""

*** Optionally used data member used to hold the original loaded data to allow comparing fields before saving.
oOrigData = .NULL.

*** When .T. checks to see what changes have occurred prior to making updates to data.
lCompareUpdates = .F.

*** A wwCollection of Validation Error Objects
oValidationErrors = .NULL.
FUNCTION oValidationErrors_access

IF ISNULL(THIS.ovalidationerrors)
    THIS.ovalidationerrors = CREATEOBJECT("wwValidationErrors")
ENDIF

RETURN THIS.ovalidationerrors


************************************************************************
*  Init
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Init()

#IF SHAREWARE
    WAIT WINDOW NOWAIT ;
        "This is a shareware copy of wwBusiness." + CHR(13)+ CHR(13) +;
        "Please register your copy."
#ENDIF
ENDFUNC
*   Init


************************************************************************
*  CreateNewId
****************************************
***  Function: Creates a new ID number for the table using the ID table.
***    Assume:
***    Return: new Id or empty or -1
************************************************************************
FUNCTION CreateNewId()

LOCAL lcOldAlias, lcPKField, lnX, lcIDTable, lcIDAlias, lnId, lnId2, lnRecno

THIS.SetError()

IF !THIS.OPEN()
    THIS.SetError("Error: Unable to open table.")
    RETURN -1
ENDIF

DO CASE
CASE THIS.ndatamode = 0
    lcPKField = THIS.cpkfield
    lcOldAlias=ALIAS()

    lcIDTable = THIS.cidtable
    lcIDAlias = JUSTSTEM(lcIDTable)

    IF !USED(lcIDAlias)
        USE (lcIDTable) IN 0 ALIAS (lcIDAlias)
        * USE (THIS.cIddatapath + lcIdTable) IN 0 ALIAS (lcIDAlias)
        IF !USED(lcIDAlias)
            THIS.SetError("Unable to open ID table: "  + THIS.ciddatapath + lcIDTable)
            IF USED(lcOldAlias)
                SELE (lcOldAlias)
            ENDIF

            RETURN -1
        ENDIF
    ENDIF

    *** Assume failure
    lnId = -1

    SET REPROCESS TO 2 SECONDS

    SELECT (lcIDAlias)
    LOCATE FOR UPPER(TABLENAME) = UPPER(THIS.calias)
    IF !FOUND()
        APPEND BLANK
        REPLACE ID WITH 0,TABLENAME WITH THIS.calias
    ENDIF

    FOR lnX=1 TO 10000    && Only allow 10000 loops to handle potential endless loops
        SELECT (lcIDAlias)
        IF RLOCK()
            IF ID < 2147483645
                REPLACE ID WITH ID + 1
            ELSE
                *** Allow for rollover -
                *** this code checks for existing ids and skips
                REPLACE ID WITH 1
            ENDIF

            lnId2 = ID

            SELE (THIS.calias)
            lnRecno = RECNO()  && Save record no

            *** Check to
            LOCATE FOR &lcPKField = lnId2
            IF FOUND()
                UNLOCK IN (lcIDAlias)
                IF lnRecno # 0 AND lnRecno <= RECCOUNT()
                    GOTO lnRecno
                ENDIF

                LOOP
            ENDIF

            *** Reset the record pointer
            IF lnRecno # 0 AND lnRecno <= RECCOUNT()
                GOTO lnRecno
            ENDIF

            SELECT (lcIDAlias)
            lnId = ID
        ENDIF
        UNLOCK IN (lcIDAlias)
        EXIT
    ENDFOR

    IF USED(lcOldAlias)
        SELE (lcOldAlias)
    ENDIF

CASE THIS.ndatamode = 2
    lnId = 0
    THIS.osql.AddParameter(THIS.cfilename,"cFileName")
    THIS.osql.AddParameter(lnId,"nID","OUT")
    IF !THIS.osql.ExecuteStoredProcedure("sp_ww_NewId")
        THIS.SetError("Unable to create unique ID. " + THIS.osql.cerrormsg)
        RETURN -1
    ENDIF
    lnId = THIS.osql.oParameters["nID"].VALUE

CASE THIS.ndatamode = 4
    PRIVATE pnID
    pnID = 0
    THIS.ohttpsql.AddSQLParameter("pnId",pnID,,.T.)

    *** Pass these parameters so the Fox client can read them
    THIS.ohttpsql.AddSQLParameter("pcPKField",THIS.cpkfield)
    THIS.ohttpsql.AddSQLParameter("pcIDTable",JUSTSTEM(THIS.cidtable))

    IF THIS.ohttpsql.execute("Execute sp_ww_NewId '" + THIS.cfilename + "',?@pnID") < 0
        THIS.SetError("Unable to create unique ID. " + THIS.ohttpsql.cerrormsg)
        RETURN -1
    ENDIF

    lnId = THIS.ohttpsql.GetSQLReturnValue("pnId")
ENDCASE

IF lnId < 1
    THIS.SetError("Unable to create unique Id.")
ENDIF

RETURN lnId
ENDFUNC
*   CreateNewId


************************************************************************
*  Load
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Load(lnpk, lnLookupType)

LOCAL loRecord, lcPKField, lnResult, lcVarType

THIS.SetError()

lcVarType = VARTYPE(lnpk)
IF lcVarType = "L"
    THIS.SetError("Load failed - no key passed.")
    RETURN .F.
ENDIF

*** Load(0) loads an empty record
IF (lcVarType = "N" AND lnpk = 0) OR lcVarType = "C" AND EMPTY(lnpk)
    RETURN THIS.New()
ENDIF

IF !THIS.OPEN()
    RETURN .F.
ENDIF

DO CASE
CASE THIS.ndatamode = 0
    lcPKField = THIS.cpkfield
    LOCATE FOR &lcPKField = lnpk

    IF FOUND()
        SCATTER NAME THIS.odata MEMO
        IF THIS.lcompareupdates
            SCATTER NAME THIS.oorigdata MEMO
        ENDIF
        THIS.nupdatemode = 1 && Edit
    ELSE
        SCATTER NAME THIS.odata MEMO BLANK
        IF THIS.lcompareupdates
            SCATTER NAME THIS.oorigdata MEMO BLANK
        ENDIF
        THIS.SetError("Load - Record not found.")
        RETURN .F.
    ENDIF
CASE THIS.ndatamode = 2 OR THIS.ndatamode = 4
    PRIVATE pnPk
    pnPk = lnpk

    IF THIS.ndatamode = 4
        loSQL = THIS.ohttpsql
        loSQL.AddSQLParameter("pnPk",pnPk)
    ELSE
        loSQL = THIS.osql
    ENDIF

    THIS.csql = "select * from " + THIS.cfilename + " where " + THIS.cpkfield + "=?pnPk"
    lnResult = loSQL.execute(THIS.csql)
    IF lnResult # 1
        IF loSQL.lerror
            THIS.SetError(loSQL.cerrormsg)
        ENDIF
        RETURN .F.
    ENDIF

    IF RECCOUNT() > 0
        SCATTER NAME THIS.odata MEMO
        IF THIS.lcompareupdates
            SCATTER NAME THIS.oorigdata MEMO
        ENDIF
        THIS.nupdatemode = 1 && Edit
    ELSE
        SCATTER NAME THIS.odata MEMO BLANK
        IF THIS.lcompareupdates
            SCATTER NAME THIS.oorigdata MEMO BLANK
        ENDIF
        THIS.SetError("No match found.")
        RETURN .F.
    ENDIF
ENDCASE

RETURN .T.
ENDFUNC
*   Load



************************************************************************
*  LoadBase
****************************************
***  Function: Internal function that can be used to load a record.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION LoadBase(lcFilter, lcFieldList)

LOCAL loRecord, lcPKField, lnResult

IF EMPTY(lcFieldList)
    lcFieldList = "*"
ENDIF

THIS.SetError()

IF !THIS.OPEN()
    RETURN .F.
ENDIF

DO CASE
CASE THIS.ndatamode = 0
    lnResult = THIS.QUERY("Select " + lcFieldList +  " from " + THIS.calias + " where " + lcFilter)
    IF lnResult > 0
        SCATTER NAME THIS.odata MEMO
        IF THIS.lcompareupdates
            SCATTER NAME THIS.oorigdata MEMO
        ENDIF
        THIS.nupdatemode = 1 && Edit
    ELSE
        SCATTER NAME THIS.odata MEMO BLANK
        IF THIS.lcompareupdates
            SCATTER NAME THIS.oorigdata MEMO BLANK
        ENDIF
        THIS.SetError("GetRecord - Record not found.")
        RETURN .F.
    ENDIF
CASE THIS.ndatamode = 2 OR THIS.ndatamode = 4
    IF THIS.ndatamode = 4
        loSQL = THIS.ohttpsql
    ELSE
        loSQL = THIS.osql
    ENDIF

    lnResult = loSQL.execute("select "  + lcFieldList + " from " + THIS.cfilename + " where " + lcFilter)
    IF lnResult # 1
        IF loSQL.lerror
            THIS.SetError(loSQL.cerrormsg)
        ENDIF
        RETURN .F.
    ENDIF

    IF RECCOUNT() > 0
        SCATTER NAME THIS.odata MEMO
        IF THIS.lcompareupdates
            SCATTER NAME THIS.oorigdata MEMO
        ENDIF
        THIS.nupdatemode = 1 && Edit
    ELSE
        SCATTER NAME THIS.odata MEMO BLANK
        IF THIS.lcompareupdates
            SCATTER NAME THIS.oorigdata MEMO BLANK
        ENDIF
        THIS.SetError("No match found.")
        RETURN .F.
    ENDIF
ENDCASE

RETURN .T.

ENDFUNC
*   LoadBase



************************************************************************
*  New
****************************************
***  Function: Creates a new oData member. Creates a PK value and sets 
***            any defaults coded in the New method.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION New(llNoNewPk)
LOCAL lcPKField, lnpk

*** Not setting lError and cErrorMsg here - let worker methods
*** do it for us. All code here
THIS.SetError()

** Create a new record object
IF !THIS.getblankrecord()
    RETURN .F.
ENDIF

IF !llNoNewPk
    lnpk = THIS.createnewid()
    IF EMPTY(lnpk)
        THIS.SetError("Couldn't create ID. " + THIS.cerrormsg)
        RETURN .F.
    ENDIF
    
    lcPKField = THIS.cpkfield
	THIS.odata.&lcPKField = lnpk
ENDIF

THIS.nupdatemode = 2 && New Record

RETURN .T.
ENDFUNC
*   New

************************************************************************
*  GetBlankRecord
****************************************
***  Function: Retrieves an empty record and creates an empty oData member. 
***            Note no defaults are set.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION GetBlankRecord()
LOCAL loRecord, lnResult

THIS.SetError()

IF !THIS.OPEN()
    RETURN .F.
ENDIF

DO CASE
CASE THIS.ndatamode = 0
    SCATTER NAME THIS.odata MEMO BLANK
CASE THIS.ndatamode = 2
    lnResult = THIS.osql.execute("select * from " + THIS.cfilename + " where 0=1")
    IF lnResult # 1
        THIS.SetError(THIS.osql.cerrormsg)
        RETURN .F.
    ENDIF
    SCATTER NAME THIS.odata MEMO BLANK
CASE THIS.ndatamode = 4
    IF THIS.ohttpsql.execute("select * from " + THIS.cfilename + " where 0=1") < 0
        THIS.SetError(THIS.ohttpsql.cerrormsg)
        RETURN .F.
    ENDIF
    SCATTER NAME THIS.odata MEMO BLANK
ENDCASE

IF THIS.lcompareupdates
    SCATTER NAME THIS.oorigdata MEMO BLANK
ENDIF

RETURN .T.
ENDFUNC
*   GetBlankRecord

************************************************************************
*  Save
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Save()
LOCAL lcPKField, llRetVal, loRecord, lcSqlCommand, loSQL, loTSql, lcSkipVars
llRetVal = .T.

THIS.SetError()

*** Optional auto Validation
IF THIS.lvalidateonsave AND ;
        !THIS.VALIDATE()
    RETURN .F.
ENDIF

loRecord = THIS.odata

IF !THIS.OPEN()
    RETURN .F.
ENDIF

lcSkipVars = EVL(THIS.cskipfieldsforupdates,"x@@!")

DO CASE
CASE THIS.ndatamode  = 0
    DO CASE
    CASE THIS.nupdatemode = 2      && New
        APPEND BLANK
        GATHER NAME loRecord MEMO FIELDS EXCEPT &lcSkipVars
        THIS.nupdatemode = 1
    CASE THIS.nupdatemode = 1      && Edit
        lcPKField = THIS.cpkfield
        LOCATE FOR &lcPKField = loRecord.&lcPKField
        IF FOUND()
            GATHER NAME loRecord MEMO FIELDS EXCEPT &lcSkipVars
        ELSE
            APPEND BLANK
            GATHER NAME loRecord MEMO FIELDS EXCEPT &lcSkipVars
        ENDIF
    ENDCASE


CASE THIS.ndatamode = 2
    LOCAL loSQL AS wwsql, llUseNamedParameters
    loSQL = THIS.osql
    loSQL.lerror = .F.

    llUseNamedParameters = WWVFPVERSION > 7

    DO CASE
    CASE THIS.nupdatemode = 2      && New
        loSQL.csql = loSQL.SQLBuildInsertStatementFromObject(loRecord,THIS.cfilename,,llUseNamedParameters)
        THIS.csql = loSQL.csql
        loSQL.execute()
        IF loSQL.lerror OR !EMPTY(loSQL.cerrormsg)
            THIS.SetError(loSQL.cerrormsg)
            RETURN .F.
        ENDIF
        THIS.nupdatemode = 1
    CASE THIS.nupdatemode = 1      && Edit
        *** Check if exists first
        lcPKField = THIS.cpkfield
        PRIVATE pvPk
        pvPk = TRANS(loRecord.&lcPKField)
        loSQL.execute("select " + lcPKField +" from " + THIS.cfilename + " where " + THIS.cpkfield + "=?pvPk" )
        IF loSQL.lerror
            THIS.SetError(loSQL.csql)
            RETURN .F.
        ENDIF

        IF RECCOUNT() < 1
            lcSqlCommand = loSQL.SQLBuildInsertStatementFromObject(loRecord,THIS.cfilename,,llUseNamedParameters)
        ELSE
            lcSqlCommand = loSQL.SqlBuildUpdateStatementFromObject(loRecord,THIS.cfilename,lcPKField,;
                IIF(THIS.lcompareupdates,THIS.oorigdata,.F.),,llUseNamedParameters )
        ENDIF

        IF !EMPTY(lcSqlCommand)
            THIS.csql = lcSqlCommand
            loSQL.execute( lcSqlCommand )
        ELSE
            loSQL.AddParameter("RESET")
        ENDIF

        IF loSQL.lerror OR !EMPTY(loSQL.cerrormsg)
            THIS.SetError(loSQL.cerrormsg)
            RETURN .F.
        ENDIF
    ENDCASE


CASE THIS.ndatamode = 4
    loSQL = THIS.ohttpsql
    loTSql = CREATEOBJECT("wwSQL")  && Command Builder

    DO CASE
    CASE THIS.nupdatemode = 2      && New
        loSQL.csql = loTSql.SQLBuildInsertStatementFromObject(loRecord,THIS.cfilename)
        loSQL.execute()
        IF loSQL.lerror
            THIS.SetError(loSQL.cerrormsg)
            RETURN .F.
        ENDIF
        THIS.nupdatemode = 1
    CASE THIS.nupdatemode = 1      && Edit
        *** Check if exists first
        lcPKField = THIS.cpkfield
        PRIVATE pvPkValue
        pvPkValue = TRANS(loRecord.&lcPKField)
        loSQL.execute("select " + lcPKField +" from " + THIS.cfilename + " where " + THIS.cpkfield + "=?pvPkValue" )
        IF loSQL.lerror
            THIS.SetError(loSQL.csql)
            RETURN .F.
        ENDIF

        IF RECCOUNT() < 1
            lcSqlCommand = loTSql.SQLBuildInsertStatementFromObject(loRecord,THIS.cfilename)
        ELSE
            lcSqlCommand = loTSql.SqlBuildUpdateStatementFromObject(loRecord,THIS.cfilename,;
                lcPKField,IIF(THIS.lcompareupdates,THIS.oorigdata,.F.))
        ENDIF
        loSQL.execute( lcSqlCommand )

        IF loSQL.lerror
            THIS.SetError(loSQL.cerrormsg)
            RETURN .F.
        ENDIF
    ENDCASE
ENDCASE

RETURN llRetVal
ENDFUNC
*   Save


************************************************************************
*  Open
****************************************
***  Function: Opens the data source or remote connection. 
***            For ODBC commands the oSQL member holds the SQL connection.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Open(lcFile, lcAlias, llForceReconnect)
LOCAL lcConnectString

lcFile=IIF(!EMPTY(lcFile),lcFile,THIS.cfilename)

*** Nothing passed and no MasterTable - exit
IF THIS.ndatamode = 0 AND EMPTY(lcFile)
    RETURN .F.
ENDIF

DO CASE
CASE THIS.ndatamode = 0
    lcAlias=IIF(!EMPTY(lcAlias),lcAlias,THIS.calias)

    IF !EMPTY(lcFile) AND UPPER(lcFile) == UPPER(THIS.cfilename)
        THIS.calias = lcAlias
    ENDIF

    IF USED(lcAlias)
        SELE (lcAlias)
    ELSE
        SELE 0
        USE ( THIS.cdatapath + lcFile ) ALIAS (lcAlias) AGAIN
        IF !USED(lcAlias)
            THIS.SetError("Unable to open " + THIS.cfilename)
            RETURN .F.
        ENDIF
    ENDIF

    *** SQL Mode
CASE THIS.ndatamode = 2
    IF ISNULL(THIS.osql) OR llForceReconnect
        THIS.osql = CREATEOBJECT("wwSQL")
        THIS.osql.EnableUnicodeToAnsiMapping(.T.)

        IF EMPTY(lcConnectString)
            lcConnectString = THIS.cconnectstring
        ENDIF

        IF !THIS.osql.CONNECT(lcConnectString)
            THIS.SetError(THIS.osql.cerrormsg)
            RETURN .F.
        ENDIF
    ENDIF
    THIS.osql.cskipfieldsforupdates = THIS.cskipfieldsforupdates
CASE THIS.ndatamode = 4
    IF ISNULL(THIS.ohttpsql)
        THIS.ohttpsql = CREATEOBJECT("wwHTTPSQL")
        THIS.ohttpsql.cserverurl = THIS.cserverurl
        * THIS.oHTTPSQL.nTransportmode = 2  && EncodeDBF cursors
        THIS.ohttpsql.cSQLConnectString = THIS.cconnectstring
        THIS.ohttpsql.cskipfieldsforupdates = THIS.cskipfieldsforupdates
    ENDIF
ENDCASE

RETURN .T.
ENDFUNC
* EOF Open



************************************************************************
*  Close
****************************************
***  Function: Closes a connection to the data base or closes
***            Fox table
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Close()

DO CASE
CASE THIS.ndatamode = 0
    IF USED(THIS.calias)
        USE IN (THIS.calias)
    ENDIF
CASE THIS.ndatamode = 2
    *** Release the SQL object
    THIS.osql = .NULL.
CASE THIS.ndatamode = 4
    THIS.ohttpsql = .NULL.
ENDCASE

ENDFUNC
*   Close





************************************************************************
*  Delete
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Delete(lnPk)
LOCAL lcPKField, lnResult

THIS.SetError()

IF !THIS.OPEN()
    RETURN .F.
ENDIF

lcPKField = THIS.cpkfield

*** Try to load the record if a PK was passed
IF EMPTY(lnpk)
    lnpk = THIS.odata.&lcPKField
ENDIF

DO CASE
CASE THIS.ndatamode = 0
    *** Delete the record
    DELETE FOR &lcPKField = lnpk
CASE THIS.ndatamode = 2
    THIS.csql = "DELETE FROM " + THIS.cfilename + " WHERE " +THIS.cpkfield + " =" + TRANSFORM(lnpk)
    lnResult = THIS.osql.execute(THIS.csql)
    IF lnResult # 1
        THIS.SetError(THIS.osql.cerrormsg)
        RETURN .F.
    ENDIF
CASE THIS.ndatamode = 4
    lnResult = THIS.ohttpsql.execute("DELETE FROM " + THIS.cfilename + " WHERE " +THIS.cpkfield + " =" + TRANSFORM(lnpk) )
    IF lnResult < 0
        THIS.SetError(THIS.osql.cerrormsg)
        RETURN .F.
    ENDIF
ENDCASE

*** And clear the data area
THIS.getblankrecord()

RETURN .T.
ENDFUNC
*   Delete





************************************************************************
*  Query
****************************************
***  Function: Queries the db and returns result set in lcCursor
***    Assume: Check .lError and .cErrorMsg for errors
***      Pass: lcSelect -  full or partial select statement
***    Return: -1 on error - record count otherwise
************************************************************************
FUNCTION Query(lcSelect, lcCursor, lnResultmode)
LOCAL lnResult

lcSelect=IIF(EMPTY(lcSelect),THIS.csql,lcSelect)
lcCursor=IIF(EMPTY(lcCursor),THIS.csqlcursor,lcCursor)
lnResultmode=IIF(EMPTY(lnResultmode),THIS.nresultmode,lnResultmode)
lcSelect = RTRIM(lcSelect)

THIS.SetError()

IF !THIS.OPEN()
    RETURN -1
ENDIF

IF EMPTY( lcSelect ) OR NOT VARTYPE( lcSelect ) = "C"
    lcSelect = THIS.csql
ENDIF
IF EMPTY( lcSelect )
    ** We get everything...
    lcSelect = "SELECT *"
ENDIF
IF ATC(" FROM",lcSelect) = 0
    IF THIS.ndatamode = 0
        lcSelect = lcSelect + " FROM  '" + ALLTRIM(THIS.cdatapath + THIS.cfilename) + "' "
    ELSE
        lcSelect = lcSelect + " FROM " + ALLTRIM(THIS.cfilename) + " "
    ENDIF
    *{lcSelect = "SELECT FROM " + Alltrim(THIS.cMasterTable) + " " + Subs(lcSelect,At(" ",lcSelect)+1)
ENDIF
IF !STARTSWITH(lcSelect,"SELECT ",.T.)
    lcSelect = "SELECT * " + lcSelect
ENDIF

THIS.csql = lcSelect

*** Run the SQL Statement
DO CASE
CASE THIS.ndatamode = 0
    IF ATC(" INTO",lcSelect) = 0
        lcSelect = lcSelect + " INTO CURSOR " + lcCursor
    ENDIF

    lcSelect = FlattenSql(lcSelect)

    &lcSelect

    IF !USED(lcCursor)
        RETURN -1
    ELSE
        lnResult = _TALLY
    ENDIF
CASE THIS.ndatamode = 2
    lcOldCursor = THIS.osql.csqlcursor
    THIS.osql.csqlcursor = lcCursor
    lnResult = THIS.osql.execute(lcSelect)
    IF lnResult < 0
        THIS.SetError(THIS.osql.cerrormsg)
        RETURN -1
    ENDIF

    THIS.osql.csqlcursor = lcOldCursor

    lnResult = RECCOUNT()
CASE THIS.ndatamode = 4
    lcOldCursor = THIS.ohttpsql.csqlcursor
    THIS.ohttpsql.csqlcursor = lcCursor
    lnResult = THIS.ohttpsql.execute(lcSelect)
    IF lnResult < 0
        THIS.SetError(THIS.ohttpsql.cerrormsg)
        RETURN -1
    ENDIF

    THIS.ohttpsql.csqlcursor = lcOldCursor
ENDCASE

*** Convert data if necessary
IF lnResultmode # 0
    THIS.ConvertData(lnResultmode,,lcCursor)
ENDIF

RETURN lnResult
ENDFUNC
* EOF - Query



************************************************************************
*  Execute
****************************************
***  Function: Executes a SQL command that returns one or more cursors.
***      Pass: lcSql     -  SQL Command to execute
***            lcCursor  - Optional - Cursor name for resultset(s)
***    Return: Record count or 0. Check .lError for errors
************************************************************************
FUNCTION Execute(lcSQL, lcCursor)
LOCAL lnResult

IF !THIS.OPEN()
    RETURN 0
ENDIF

IF EMPTY(lcCursor)
   lcCursor = this.cSqlCursor
ENDIF

THIS.lerror = .F.

DO CASE
CASE THIS.ndatamode = 0
    lcSQL = FlattenSql(lcSQL)

    *** Run the SQL Statement - note there's no INTO clause
    *** added by default to Fox data. If you need this you
    *** should use Query() instead or else make sure you
    *** always issue 'raw' SQL strings
    &lcSQL

    IF THIS.lerror
        lnResult = 0
    ELSE
        lnResult = _TALLY
    ENDIF
CASE THIS.ndatamode = 2
    THIS.osql.csqlcursor = lcCursor
    lnResult = THIS.osql.execute(lcSQL)

    *** Always reset the cursor afterwards
    THIS.csqlcursor = "TSQLQuery"

    IF lnResult < 0
        THIS.SetError(THIS.osql.cerrormsg)
        RETURN 0
    ENDIF

    lnResult = THIS.osql.nAffectedRecords
CASE THIS.ndatamode = 4
    THIS.ohttpsql.csqlcursor = lcCursor
    lnResult = THIS.ohttpsql.execute(lcSQL)
    
    *** Always reset the cursor afterwards
    THIS.csqlcursor = "TSQLQuery"

    IF THIS.ohttpsql.lerror
        THIS.SetError(THIS.ohttpsql.cerrormsg)
        RETURN 0
    ENDIF
    IF lnResult < 0
        THIS.ohttpsql.cerrormsg
        RETURN 0
    ENDIF
ENDCASE

RETURN lnResult
ENDFUNC
*   Execute


************************************************************************
*  ExecuteNonQuery
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION ExecuteNonQuery(lcSQL)

*** Just for cleaner naming of the method
RETURN This.Execute(lcSql)
ENDFUNC
*   ExecuteNonQuery


************************************************************************
*  Find
****************************************
***  Function: Tries to find a record based on a filter string 
***            and sets the oData member. Only the first item found is
***            returned so you probably want to reserve this for known 
***            lookups of unique items.
***    Assume:
***      Pass: lcFilter       -  SQL Filter string (WHERE)
***            llNoDataMember -  if .T. doesn't set the .oData member
***    Return: .T. or .F. with oData set
************************************************************************
FUNCTION Find(lcFilter, llNoDataMember)
LOCAL lcCmd, lnResult

IF !THIS.OPEN()
    RETURN .F.
ENDIF

DO CASE
CASE THIS.ndatamode = 0
    lcCmd = "LOCATE FOR " + lcFilter
    &lcCmd

    IF FOUND()
        IF !llNoDataMember
	        SCATTER NAME THIS.odata MEMO
	        IF THIS.lcompareupdates
	            SCATTER NAME THIS.oorigdata MEMO
	        ENDIF
        ENDIF
        RETURN .T.
    ELSE
        IF !llNoDataMember
	        SCATTER NAME THIS.odata BLANK MEMO
	    ENDIF
    ENDIF
CASE THIS.ndatamode = 2
    THIS.osql.csqlcursor = "TSQLQuery"
    THIS.osql.csql = "select * from " + THIS.cfilename + " WHERE " + lcFilter
    *_cliptext = THIS.oSQL.cSQL
    lnResult = THIS.osql.execute()

    IF lnResult # 1
        THIS.SetError(THIS.osql.cerrormsg)
        RETURN .F.
    ENDIF
    IF RECCOUNT() > 0
	    IF !llNoDataMember
	        SCATTER NAME THIS.odata MEMO
	        IF THIS.lcompareupdates
	            SCATTER NAME THIS.oorigdata MEMO
	        ENDIF
	    ENDIF
        RETURN .T.
    ELSE
	    IF !llNoDataMember
	        SCATTER NAME THIS.odata BLANK MEMO
	    ENDIF
    ENDIF
ENDCASE

RETURN .F.
ENDFUNC
*   Find


************************************************************************
*  Validate
****************************************
***  Function: Virtual method that can be used to hook up
***            validation logic. No implementation in the base class.
***            This method typically contains code that checks the content
***            of the oData member. Note you have to call this 
***            method manually - it isn't called from the Save()
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Validate()
*** Must implement code in subclass
*** Should call AddValidationError() to set any validation errors

* this.oValidationerrors.Clear()
RETURN .T.
ENDFUNC
*   Validate


************************************************************************
*  AddValidationError
****************************************
***  Function: Adds a validation error to the oValidationErrors collection. 
***            Pass error string and object name (optional)
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION AddValidationError(lcError, lcCtlId)

loError = CREATEOBJECT("wwValidationError")
loError.cMessage = lcError
IF !EMPTY(lcCtlId)
    loError.cObjectName = lcCtlId
ENDIF

THIS.ovalidationerrors.ADD( loError )

ENDFUNC
*   AddValidationError







************************************************************************
*  GetProperty
****************************************
***  Function: Retrieves a property out of the XML field. 
***            Properties can be written with SetProperty.
***    Assume:
***      Pass: lcProperty - previous set property to retrieve
***    Return: value
************************************************************************
FUNCTION GetProperty(lcProperty)

LOCAL loXMLState

loXMLState = CREATEOBJECT("wwXMLState")
loXMLState.LOADXML(THIS.odata.XML)

RETURN loXMLState.getproperty(lcProperty)
ENDFUNC
*   GetProperty


************************************************************************
*  SetProperty
****************************************
***  Function: Sets a property value in the XML field. 
***            Note: for performance reasons no checks are performed 
***            whether the XML field exists.
***    Assume:
***      Pass: lcProperty  -   Property to set
***            lvValue     -   value of any simple type to set
***    Return: nothing
************************************************************************
FUNCTION SetProperty(lcProperty, lcValue)

LOCAL loXMLState
loXMLState = CREATEOBJECT("wwXMLState")
loXMLState.LOADXML(THIS.odata.XML)
loXMLState.setproperty(lcProperty,lcValue)
THIS.odata.XML = loXMLState.GetXML()

ENDFUNC
* SetProperty







************************************************************************
*  StatusMessage
****************************************
***  Function: Displays messages related to the business object
***            Implement in your subclass to write out where it
***            needs to (a statusbar log etc.)
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION StatusMessage(lcMessage)
WAIT WINDOW NOWAIT lcMessage
ENDFUNC
*   StatusMessage


************************************************************************
*  SetError
****************************************
***  Function: Sets the error message on the object
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION SetError(lcErrorMsg, lnError)

IF PCOUNT() = 0
    THIS.cerrormsg = ""
    THIS.lerror = .F.
    RETURN
ENDIF

THIS.cerrormsg = lcErrorMsg
THIS.lerror = .T.
ENDFUNC
*   SetError


************************************************************************
*  ConvertData
****************************************
***  Function: Converts output into the requested resultmode. 
***      Pass: lnMode
***            0-Cursor 1-XML 2-XML w/Schema 3-XML w/DTD 11-oData XML 
***            12-oData XML w/Schema 13-oData XML w/DTD 20-Hierarchical XML 
***            30-ADO 40-EncodeDBF
***            50 - Raw JSON  51 - Indented JSON
***            lcDocRoot  -  XML Doc root
***            lcTable    -  XML Table Node
***            lcRow      -  Xml Record Node
***    Return: nothing - result is in vResult
************************************************************************
FUNCTION ConvertData(lnResultmode, lcDocRoot, lcTable, lcRow)
LOCAL loXML

IF EMPTY(lnResultmode)
    *** VFP Cursor mode - just return
    RETURN .T.
ENDIF

IF EMPTY(lcRow)
    lcRow = "datarow"
ENDIF
IF EMPTY(lcDocRoot)
    lcDocRoot = "sqlresult"
ENDIF
IF EMPTY(lcTable)
    IF lnResultmode < 10
        lcTable = LOWER(ALIAS())
    ELSE
        lcTable = LOWER(THIS.CLASS)
    ENDIF
ENDIF

DO CASE
    *** Cursor
CASE lnResultmode = 1
    loXML = CREATEOBJECT("wwXML")
    loXML.cDocRootName = lcDocRoot

    THIS.cresultxml = loXML.CURSORTOXML(lcTable, lcRow)
    THIS.vresult = THIS.cresultxml

    *** Cursor with Schema
CASE lnResultmode = 2
    loXML = CREATEOBJECT("wwXML")
    loXML.cDocRootName = lcDocRoot

    loXML.nCreateDataStructure = 1
    THIS.cresultxml = loXML.CURSORTOXML(lcTable,lcRow)
    THIS.vresult = THIS.cresultxml

    *** Cursor with DTD
CASE lnResultmode = 3
    loXML = CREATEOBJECT("wwXML")
    loXML.cDocRootName = lcDocRoot

    loXML.nCreateDataStructure = 2
    THIS.cresultxml = loXML.CURSORTOXML(lcTable,lcRow)
    THIS.vresult = THIS.cresultxml

    *** VFP's Native CursorToXML without schema
CASE lnResultmode = 5
    CURSORTOXML(SELECT(),"THIS.cResultXML",1,32,0,"")

    *** VFP's CursorToXML with Schema
CASE lnResultmode = 6
    CURSORTOXML(SELECT(),"THIS.cResultXML",1,32,0,"1")
    *** DataSet
CASE lnResultmode = 8
    LOCAL oXA AS XMLADAPTER
    oXA = CREATEOBJECT("XMLAdapter")
    oXA.ADDTABLESCHEMA(ALIAS(),.T.,STRCONV(lcTable,5))
    oXA.TOXML("THIS.cResultXML")

    *** Plain Object
CASE lnResultmode = 11
    loXML = CREATEOBJECT("wwXML")
    loXML.cDocRootName = lcDocRoot

    THIS.cresultxml = loXML.ObjectToXML(THIS.odata,lcTable)
    *** Plain Object with DTD

    *** Plain Object with Schema
CASE lnResultmode = 12
    loXML = CREATEOBJECT("wwXML")
    loXML.cDocRootName = lcDocRoot

    loXML.nCreateDataStructure = 1
    THIS.cresultxml = loXML.ObjectToXML(THIS.odata,lcTable)

    *** Plain Object with DTD
CASE lnResultmode = 13
    loXML = CREATEOBJECT("wwXML")
    loXML.cDocRootName = lcDocRoot

    loXML.nCreateDataStructure = 2
    THIS.cresultxml = loXML.ObjectToXML(THIS.odata,lcTable)

    *** Hierarchical Object
CASE lnResultmode = 20
    loXML = CREATEOBJECT("wwXML")
    loXML.cDocRootName = lcDocRoot

    loXML.lRecurseObjects = .T.
    loXML.cPropertyExclusionList = loXML.cPropertyExclusionList + ;
        ",cpkfield,cskipfieldsforupdates,ndatamode,lerror,nresultmode,calias,cfilename,cdatapath,csql,csqlcursor,cerrormsg,cresultxml,cconnectstring,cidtable,nupdatemode,osql,vresult,"
    loXML.cDocRootName = lcDocRoot
    THIS.cresultxml = loXML.ObjectToXML(THIS,lcTable)
    THIS.vresult = THIS.cresultxml

    *** Miscellaneous Exports

    *** ADO Code here
CASE lnResultmode = 30
    loXML = CREATEOBJECT("wwXML")
    loXML.cDocRootName = lcDocRoot

    lcXML = loXML.CursorToADOXML()
    lcFile = SYS(2023) + "\" + SYS(2015) + ".xml"
    File2Var(lcFile,loXML.EncodeXML(lcXML))
    oRS = CREATEOBJECT("AdoDB.RecordSet")
    oRS.OPEN(lcFile)  && Load as XML document
    THIS.vresult = oRS

    ERASE (lcFile)

CASE lnResultmode = 40
    lcFile =SYS(2023) + "\" + SYS(2015) + ".dbf"

    *** Must copy file so we can read it
    *** it could be cursor so just closing won't
    *** work
    COPY TO ( lcFile )

    THIS.vresult = EncodeDBF(lcFile,.T.)

    ERASE (FORCEEXT(lcFile,"*"))

    *** Collection
CASE lnResultmode = 64
    THIS.vresult = CursorToCollection(ALIAS())
    USE IN ALIAS()
    
    *** JSON (raw)
CASE lnResultmode = 50
    loSer = CREATEOBJECT("wwJsonSerializer")
    THIS.vresult = loSer.Serialize("cursor:" + ALIAS())
    
    *** Formatted JSON
CASE lnResultmode = 51
    loSer = CREATEOBJECT("wwJsonSerializer")
    THIS.vresult = loSer.Serialize("cursor:" + ALIAS(),.T.)
ENDCASE

RETURN .T.
ENDFUNC
*   ConvertData



************************************************************************
*  LoadFromXml
****************************************
***  Function: Loads the object from an XML source. 
***    Assume:
***      Pass: lcXml  - XML to load from
***            lnMode -
***               0 - load oData member, 
***               2 - load entire object recursively (requires object to be pre-loaded with property values)
***    Return:
************************************************************************
FUNCTION LoadFromXml(lcXML, lnMode)

IF VARTYPE(lnMode) # "N"
    lnMode = 0
ENDIF

DO CASE
    *** Full object - note object should be pre-created
CASE lnMode = 2
    loXML = CREATEOBJECT("wwXML")
    loXML.lRecurseObjects = .T.
    lvResult = loXML.XMLToObject(lcXML,THIS)
    IF loXML.lerror
        THIS.SetError(loXML.cerrormsg)
        RETURN .F.
    ENDIF
OTHERWISE
    *** load the oData member
    loXML = CREATEOBJECT("wwXML")
    lvResult = loXML.XMLToObject(lcXML,THIS.odata)
    IF loXML.lerror
        THIS.SetError(loXML.cerrormsg)
        RETURN .F.
    ENDIF
ENDCASE

RETURN .T.
ENDFUNC
*   LoadFromXml


************************************************************************
*  ImportData
****************************************
***  Function: Imports data exported with ConvertData. 1-XML from Cursor 2-XML w/Schema 3-XML w/DTD 11-oData XML 12-oData XML w/Schema 13-oData XML w/DTD 20-Hierarchical XML 40-DecodeDBF
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION ImportData(lnMode, lcXML, lcAlias)
LOCAL lcXML

IF EMPTY(lnMode)
    THIS.SetError("No parameter passed to importdata")
    RETURN .F.
ENDIF

DO CASE
    *** XML with no Schema/DTD
CASE lnMode = 1
    IF EMPTY(lcAlias)
        lcAlias = "TXMLResult"
    ENDIF
    IF !USED(lcAlias)
        *** Have to create the cursor from the current table
        THIS.LOAD(0)
        #IF VERSION(5) > 600
            SELECT * FROM (ALIAS()) WHERE .F. INTO CURSOR (lcAlias) NOFILTER READWRITE
        #ELSE
            SELECT * FROM (ALIAS()) WHERE .F. INTO CURSOR (lcAlias + "_1")  NOFILTER
            WrCursor(lcAlias) && Make writeable
        #ENDIF
    ENDIF
    loXML = CREATEOBJECT("wwXML")
    RETURN loXML.XMLTOCURSOR(lcXML,lcAlias)

    *** XML with Schema or DTD
CASE lnMode = 2 OR lnMode =3
    loXML = CREATEOBJECT("wwXML")
    RETURN loXML.XMLTOCURSOR(lcXML,lcAlias)

    *** VFP 7 XMLTOCursor
CASE INLIST(lnMode,5,6)
    IF EMPTY(lcAlias)
        lcAlias = "TXMLResult"
    ENDIF
    XMLTOCURSOR(lcXML,lcAlias)
    RETURN .T.

    *** Object without DTD/SCHEMA - lcAlias = object reference
CASE lnMode = 11 OR lnMode = 12 OR lnMode = 13
    loXML = CREATEOBJECT("wwXML")
    RETURN loXML.XMLToObject(lcXML,THIS.odata)

CASE lnMode = 20
    loXML = CREATEOBJECT("wwXML")
    loXML.lRecurseObjects = .T.
    RETURN loXML.XMLToObject(lcXML,THIS)

    *** Encode DBF created string
CASE lnMode = 40
    IF EMPTY(lcAlias)
        lcAlias = "TXMLResult"
    ENDIF
    lcFile = SYS(2023) + "\" + SYS(2015) + ".dbf"

    llResult = DecodeDBF(lcXML,lcFile)
    IF llResult
        USE (lcFile) IN 0 ALIAS __IMPORT
        #IF VERSION(5) > 600
            SELECT * FROM __IMPORT INTO CURSOR (lcAlias) NOFILTER READWRITE
        #ELSE
            SELECT * FROM __IMPORT INTO CURSOR (lcAlias) NOFILTER
            WrCursor()
        #ENDIF
        USE IN __IMPORT
    ENDIF

    ERASE (FORCEEXT(lcFile,"*"))
    RETURN llResult
ENDCASE

RETURN .T.
ENDFUNC
*   ImportData



************************************************************************
*  CreateChildObject
****************************************
***  Function: Creates a Child Object and sets the appropriate connection properties by passing them on to the child object.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION CreateChildObject(lcClass)
LOCAL loObject

IF VARTYPE(lcClass) = "O"
    loObject = lcClass
ELSE
    loObject = CREATEOBJECT(lcClass)
ENDIF

loObject.ndatamode = THIS.ndatamode

DO CASE
CASE THIS.ndatamode = 0
    loObject.ndatamode = 0
    loObject.cdatapath = THIS.cdatapath
CASE THIS.ndatamode = 2
    loObject.cconnectstring = THIS.cconnectstring
    loObject.setsqlobject(THIS.osql)
CASE THIS.ndatamode = 4
    loObject.ndatamode = 4
    loObject.cserverurl = THIS.cserverurl
    loObject.ohttpsql = THIS.ohttpsql
ENDCASE

RETURN loObject
ENDFUNC
*   CreateChildObject


************************************************************************
*  SetSqlObject
****************************************
***  Function: Assigns existing SQL object to the oSQL object or 
***            creates one if a connection string is passed. Handles updating data mode and other admin tasks.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION SetSqlObject(lvSQL)

IF VARTYPE(lvSQL) = "O"
    THIS.osql = lvSQL
    THIS.cconnectstring = THIS.osql.cconnectstring
    THIS.ndatamode = 2
    RETURN .T.
ENDIF

IF VARTYPE(lvSQL) = "C"
    IF ISNULL(THIS.osql)
        THIS.osql = CREATEOBJECT("wwSQL")
    ENDIF
    THIS.ndatamode = 2
    IF !THIS.osql.CONNECT(lvSQL)
        RETURN .F.
    ENDIF
ENDIF

RETURN .T.
ENDFUNC
*   SetSqlObject

************************************************************************
*  SetComEnvironment
****************************************
***  Function: Sets the default com environment when running under COM and
***            ASP especially.This method can be called from Init.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION SetComEnvironment()

*** Make all required environment settings here
*** KEEP IT SIMPLE: Remember your object is created
***                 on EVERY ASP page hit!
SET RESOURCE OFF  && best to do in compiled-in CONFIG.FPW

SET EXCLUSIVE OFF
SET REPROCESS TO 2 SECONDS
SET CPDIALOG OFF

SET DELETED ON
SET EXACT OFF
SET SAFETY OFF

*** Retrieve the DLL location and grab just the path to save!
IF APPLICATION.STARTMODE = 3 OR APPLICATION.STARTMODE = 5
    THIS.cappstartpath = ADDBS(JUSTPATH(APPLICATION.SERVERNAME))
ELSE
    THIS.cappstartpath = SYS(5) + ADDBS(CURDIR())
ENDIF

*** Add the startup path to the path list!
*** Add any additional relative paths as required
DO PATH WITH THIS.cappstartpath


ENDFUNC






************************************************************************
*  CreateTable
****************************************
***  Function: Create the primary table for this object. Should be
***            implemented by each business object. Auto-generated
***            by Wizard but has to be kept up to date.
***    Assume: 
***      Pass:  lcFilename  -  name of the file to create
***    Return:
************************************************************************
FUNCTION CreateTable(lcFilename)
ENDFUNC
*   CreateTable


************************************************************************
*  UpdateStructure
****************************************
***  Function: Updates the structure of the table to match the
***            structure defined in CreateTable.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION UpdateStructure()

IF THIS.ndatamode # 0
    THIS.SetError("Update Structure is supported only for physical Fox tables...")
    RETURN .F.
ENDIF

IF EMPTY(THIS.cfilename)
    *** Nothing to do here
    RETURN .T.
ENDIF
IF EMPTY(THIS.cdatapath)
    THIS.cdatapath = ADDBS( JUSTPATH( FULLPATH(FORCEEXT(THIS.cfilename,"dbf") ) ) )
ENDIF

*** Check if we can get it open EXCLUSIVE
IF !OpenExclusive(THIS.cdatapath + THIS.cfilename,THIS.calias)
    RETURN .F.
ENDIF

SELE (THIS.calias)
USE

*** Save old file name
lcFileName = THIS.cfilename
lcDatapath = THIS.cdatapath

THIS.cfilename = SYS(2015)
THIS.cdatapath = SYS(2023) + "\"

*** Create the new table
THIS.createtable(THIS.cdatapath + THIS.cfilename)

*** Must Open again
THIS.OPEN()

*** Append from the old File
APPEND FROM (lcDatapath + lcFileName)

*** Reindx now
THIS.REINDEX()
THIS.OPEN()

*** Copy the file ontop of the old one
COPY TO (lcDatapath + lcFileName) WITH CDX

*** Close and clean up
USE
ERASE( THIS.cdatapath + THIS.cfilename + ".*" )

*** Reset to current cursor
THIS.cfilename = lcFileName
THIS.cdatapath = lcDatapath

RETURN .T.
ENDFUNC
* UpdateStructure

************************************************************************
*  Reindex
****************************************
***  Function: Used to PACK and Reindex the data files. This method 
***            should be implemented by all business objects. The default
***            behavior simply PACKs the file.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Reindex(llReturnIndexString)

*** Only do this with Fox data
IF THIS.ndatamode # 0
    RETURN .T.
ENDIF

IF !OpenExclusive(THIS.cdatapath + THIS.cfilename,THIS.calias)
    RETURN .F.
ENDIF

SELE (THIS.calias)

*** Build an output string for all the index expressions
lnX = 1
lcOutStr = ""
DO WHILE !EMPTY(TAG(lnX))
    lcOutStr = lcOutStr + ;
        "INDEX ON "+LOWER(SYS(14,lnX))+;
        " TAG "+LOWER(TAG(lnX))+;
        IIF(!EMPTY(SYS(2021,lnX))," FOR "+SYS(2021,lnX),"")

    IF DESCENDING(ALIAS(), lnX)
        lcOutStr = lcOutStr + " DESCENDING"
    ENDIF
    IF UNIQUE(ALIAS(), lnX)
        lcOutStr = lcOutStr + " UNIQUE"
    ENDIF

    lcOutStr=lcOutStr+CHR(13)
    lnX = lnX + 1
ENDDO


IF !EMPTY(lcOutStr)
    DELETE TAG ALL

    *** Now run the individual index commands
    EXECSCRIPT(lcOutStr)
ENDIF

PACK

USE

IF llReturnIndexString
    THIS.vresult = lcOutStr
ENDIF

RETURN .T.
ENDFUNC
*   Reindex


************************************************************************
*  Backup
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Backup(lcPath, llCreate)

THIS.SetError()

*** Retrieve all records
lnResult = THIS.QUERY()
IF !DIRECTORY(lcPath)
    IF llCreate
        MD (lcPath)
    ELSE
        THIS.SetError("Backup Directory doesn't exist.")
        RETURN .F.
    ENDIF
ENDIF

COPY TO (ADDBS(lcPath) + THIS.cfilename)

*** Close the cursor
USE
ENDFUNC
* Backup



ENDDEFINE
* EOC wwBusinessObject


*************************************************************
DEFINE CLASS wwBusinessChildCollection AS wwBusinessObject
*************************************************************
*: Author: Rick Strahl
*:         (c) West Wind Technologies, 2018
*:Contact: http://www.west-wind.com
*:Created: 11/09/2018
*************************************************************

*-- Number of items in the list.
ncount = 0
FUNCTION nCount_Access()
RETURN THIS.oRows.COUNT
ENDFUNC

*-- Array that holds the record data. Each item contains a SCATTER NAME type record.
oRows = NULL
FUNCTION oRows_Access
IF ISNULL(THIS.oRows)
    THIS.oRows = CREATEOBJECT("Collection")
ENDIF
RETURN THIS.oRows
ENDFUNC



************************************************************************
*  Add
****************************************
***  Function: Adds a new member to a Collection.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Add(loItem)
THIS.oRows.ADD(loItem)
ENDFUNC
*   Add


************************************************************************
*  Remove
****************************************
***  Function:
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Remove(lnIndex)

IF lnIndex < 1 OR lnIndex > THIS.nCount
    RETURN .F.
ENDIF

this.oRows.Remove(lnIndex)

RETURN .T.
ENDFUNC
*   Remove

************************************************************************
*  Clear
****************************************
***  Function: Clears out the collection of items
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Clear()
LOCAL loRows 

loRows = this.oRows
RELEASE loRows

this.oRows = CREATEOBJECT("Collection")
ENDFUNC
*   Clear


************************************************************************
*  Item
****************************************
***  Function: Retrieves an item by index count.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION Item(lnIndex)

IF lnIndex < 1 OR lnIndex > THIS.oRows.Count
    RETURN .NULL.
ENDIF

RETURN THIS.oRows.Item(lnIndex)
ENDFUNC
*   Item



************************************************************************
*  LoadFromCursor
****************************************
***  Function: Loads the object from the currently selected cursor.
***    Assume:
***      Pass:
***    Return:
************************************************************************
FUNCTION LoadFromCursor(llLoadBusinessObjects, lcCursor)
LOCAL x, loItem, lcOldCursor

lcOldCursor = ""
IF !EMPTY(lcCursor)   
   lcOldCursor = ALIAS()
   SELECT (lcCursor)
ENDIF

SCAN
  IF llLoadBusinessObjects
	 loItem = CREATEOBJECT(this.Class)
	 lcPk = loItem.cPkField
	 loitem.Load( EVALUATE(lcPK) )
  ELSE
   	  SCATTER NAME loItem MEMO   	  
  ENDIF
  this.oRows.Add(loItem)
ENDSCAN

IF !EMPTY(lcOldCursor)
   SELECT (lcOldCursor)
ENDIF

RETURN this.oRows.Count
ENDFUNC
*   LoadFromCursor


************************************************************************
*  SaveToCursor
****************************************
***  Function: Saves each of the items in the array to disk using
***            individual item Save() calls. Loads the oData member 
***            for each operation then performs a Save for each.
***    Assume: You're responsible for removing elements that 
***            are have been removed from the collection. This
***            routine only updates existing items or adds new ones
***      Pass:
***    Return: .T. or .F.
************************************************************************
FUNCTION SaveToCursor(llCollectionHoldsClass, llUseTransaction)
LOCAL x, loData, loItem

IF THIS.oRows.Count = 0
    RETURN .T.
ENDIF

IF llUseTransaction
   BEGIN TRANSACTION
   
   *** Recursive - execute without transaction
   IF !this.SaveToCursor(llCollectionHoldsClass,.F.)
      ROLLBACK
      RETURN .F.
   ENDIF  
   
   END TRANSACTION
   
   RETURN .T.
ENDIF


*** Save current data item
loData = THIS.oData

FOR x = 1 TO THIS.ncount
    IF llCollectionHoldsClass
    	loItem = this.oRows.Item(x)
        IF !loItem.Save()
            THIS.SetError(loitem.cErrorMsg)
            RETURN .F.
        ENDIF
    ELSE
        *** Load into 'this' and Save()
        THIS.oData = THIS.oRows.Item(x)
        IF !THIS.SAVE()
            RETURN .F.
        ENDIF
    ENDIF
ENDFOR

*** Restore data item
THIS.oData = loData

RETURN .T.
ENDFUNC
*   SaveToCursor

ENDDEFINE
*
*-- EndDefine: wwitemlist
**************************************************
