Issues when upploading a document to a datasource/Environment with required attributes

Hi!

I’m trying to add a document to a datasource, but when I try and add it using aaApi_CreateDocument() it fails…

The reason is that there are some attributes are required or requires a value != null and I guess they don’t have a default value set.

Is there a way to upload a new document and set required attributes simultainously?

 

If I try and add a document manually without using a wizard I get the same issue, “Last Error [ 58116 ] Error creating new document. Required field has no value.” so the user is forced to use the wizard.

Any help would be greatly appreciated!

/Per

Parents
  • It would really be helpful if you posted a code snippet to show how you are using the function. I would encourage anyone with "problems" with code, to post code snippets to help others help you as it really doesn't help to say "something doesn't work, why is that?" Please don't take this personally, as it is very common, and we all do it at some point and to some degree.

    I suspect that the environment for the folder where you are trying to create a document is configured to create an attribute row when a document is created and that you are not accounting for that in your code. What are you are specifying the value for the parameter "ulFlags"?

    If you haven't already done so, try setting this parameter to AADMSDOCCREF_NO_ATTR_RECORD and see if that "fixes" your problem. As the developer, you need to make sure that you follow the intention of the various datasource and user settings as not all of them (most are not) enforced at the API level as that is what makes using the API functions so powerful as you can "override" default behavior (but not security and some datasource or user settings).

    To answer your question about "is it possible", no, but you can make it look like it is as that is what the Wizard is doing.  It creates the document first, but has the information that it needs to create the attribute row, so after creating the document without an attribute row, it then creates the attribute row using the values that the user specified in the wizard (and it probably first validated that those values would work).  You probably should do the same in your code.  Make sure that you have (or know) any required attribute values before creating the document so that you (the developer) don't break the intention of the PW Administrator of not having documents created without an attribute row.

    Answer Verified By: Per Bodin - AEC AB 

  • Hi Dan!

    Thank you!! Perfect, that was exactly what I was looking for, worked like a charm. We’re now able to upload files even if attributes are required.

    I’d gone the long way and tried collected all the attribute-definitions and all their properties to check if they had any required parameters and was looking for a way to set those at upload time. But, now I can upload without setting them, with the option to use my code to identify and warn and possibly fill out the required values afterwards if they are available.

    Of course I should have supplied a snippet as you say, I was just a bit frustrated at the time an just threw a question out there to see if someone could help, for reference here is some code: 
    Working in both c++ and writing a wrapper in c# but here’s a snip of the c++ bit with the magic attribute hardcoded.

    bool bOK = aaApi_CreateDocument(&docID, _CurrentProjectId, 0, 0, AADMS_ITYPE, lngAppID, NULL, lngWorkSpaceID, bstrTemplateFile, bstrFileNameWithExtension, bstrName, bstrComments, NULL, FALSE, AADMSDOCCREF_NO_ATTR_RECORD, strWorkingDir, _MAX_PATH - 1, &AttributeId);

    Thanks again!!!
  • Happy to help, and happier to know that you got your code working!
Reply Children
  • Hi,

    I have tried this method and it works fine to create new document, but then I have a problem to update attribute values.

    Is it possible create document with required fields (document code) and write attribute data values?

    Thanks you.

  • Darius,

    "Words are getting in the way" again.  I'm not sure what you are trying to accomplish since you can't update attribute values if they don't exist, and you say "I have tried this method and it works fine to create a new document", i.e., you create a document without attributes, etc.

    I think you mean that you want to create the attributes after the document has been created without any attributes, and that some attributes are required, and there is a document code involved.

    The "good news" is that you can create document codes with the API.  Look under Modules -> ProjectWise DMS API -> Document Code...

    The "not so good news" is that you will need to follow whatever the rules are for the document coding for the document's environment and that probably requires you (the developer) to understand what the contraints are for providing any parts of the code for the context.

    If this is done as an extenion to ProjectWise Explorer, then you can always prompt the user for the details.  If this is a "background" tool, then you will need to determine what the rules are for creating the document code for a document and then implement those rules.

    I've done such a thing, but as most things goes, the 80/20 rule comes into play, i.e. you get the requirements and implement them, and then "discover" that you only implemented 80% of what the user needed because you didn't know all the exceptions to the rules...

    As for "writing" attribute values, i.e. creating attribute, there are three steps:

    1. Initialize the INSERT buffer

    2 .Populate the INSERT buffer

    3. Write the buffer to the database.

    The actual process to do this is a bit involved.  You need information about the table behind the environment, as well as other information.

    Updating attributes is similar:

    1. Initialize the UPDATE buffer

    2. Populate the UPDATE buffer

    3. Write the buffer to the database.

    Here's the code I provide for the solution to the related lecture and exercies in the PW SDK class I teach:

    /****************************************************************************
    *
    *             ProjectWise(TM) CONNECT Edition Software Development Kit
    *             Sample Application
    *             Copyright (C) 2018 Bentley Systems, Incorporated
    *             All Rights Reserved
    *
    ****************************************************************************/
    // Solution for exercise "Working with Attributes"
    // MyAttributes01.cpp : Defines the initialization routines for the DLL.
    //
    
    #include "stdafx.h"
    #include "MyAttributes01.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
    
    //
    //TODO: If this DLL is dynamically linked against the MFC DLLs,
    //		any functions exported from this DLL which call into
    //		MFC must have the AFX_MANAGE_STATE macro added at the
    //		very beginning of the function.
    //
    //		For example:
    //
    //		extern "C" BOOL PASCAL EXPORT ExportedFunction()
    //		{
    //			AFX_MANAGE_STATE(AfxGetStaticModuleState());
    //			// normal function body here
    //		}
    //
    //		It is very important that this macro appear in each
    //		function, prior to any calls into MFC.  This means that
    //		it must appear as the first statement within the 
    //		function, even before any object variable declarations
    //		as their constructors may generate calls into the MFC
    //		DLL.
    //
    //		Please see MFC Technical Notes 33 and 58 for additional
    //		details.
    //
    
    
    // CMyAttributes01App
    
    BEGIN_MESSAGE_MAP(CMyAttributes01App, CWinApp)
    END_MESSAGE_MAP()
    
    
    // CMyAttributes01App construction
    
    CMyAttributes01App::CMyAttributes01App()
    {
    	// TODO: add construction code here,
    	// Place all significant initialization in InitInstance
    }
    
    
    // The one and only CMyAttributes01App object
    
    CMyAttributes01App theApp;
    
    
    // CMyAttributes01App initialization
    
    BOOL CMyAttributes01App::InitInstance()
    {
    	CWinApp::InitInstance();
    
    	return TRUE;
    }
    
    int CMyAttributes01App::ExitInstance()
    {
    	// TODO: Add your specialized code here and/or call the base class
    
    	return CWinApp::ExitInstance();
    }
    
    /******************************************************************************
    Exercise 1, Step 4
    Set a breakpoint at the beginning of this function.
    *******************************************************************************/
    extern "C" int WINAPI DocCmd_CreateNewSheet
    (
    unsigned int count,
    long*  pProjects,
    long*  pDocuments
    )
    {
    	AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
        /******************************************************************************
        Exercise 2, Step 1
    	Create a loop to process each document.  In the real world, you would want
    	to give the user a way to cancel the loop if you show each error, or a way
    	to process all of the documents and review the errors later.  We won't do 
    	that in this exercise.  Just show errors as you go and don't worry about
    	giving the user a way to cancel the processing, just stop on the first error.
    
        Hints:  Use aaApi_SetLastError() and aaApi_ShowLastError() for custom error
        messages.  To make your error codes unique from ProjectWise, you should use 
        AAERR_USERERROR_FIRST as your starting point for your custom error codes.
    	******************************************************************************/
    	for (LONG i=0; i<(LONG)count; i++)
    	{
            /******************************************************************************
            Exercise 2, Step 2
    		We need some information about the environment.  
            
            Hint: We can get two key items that we need from the document's folder by using 
            aaApi_GetEnvTableinfoByProject().  
    		******************************************************************************/
            LONG	lEnvId = 0L;
            LONG	lTableId = 0L;
            LONG	lAttrColId = 0L;    // the column id that holds the attribute id's value
    
            if (!aaApi_GetEnvTableInfoByProject (*(pProjects+i), &lEnvId, &lTableId, &lAttrColId))
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+1, 
    								L"Could not get environment info.", 
    								L"Problem with selecting environment info using project id.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+1;
    		}
    
    		/**************************************************************************
            Exercise 2, Step 3
    		We need to determine the column ids that we will need for our values. Since
            the documents passed to use could be from different folders with different
            environments, we will look these up for each document to keep things simple.
    
            Hints: Use aaApi_SelectColumnsByTable(), then get the names of each using
            aaApi_GetColumnStringProperty() and when you find a match, use
            aaApi_GetColumnNumericProperty() and save that column id to a local variable
            with a meaninful name like LONG lo_string10ColId.  We will need it later.
            Make sure you add error handling, but keep it simple by displaying your own
            error message and then returning from the function.
    		***************************************************************************/
    		LONG	lo_string10ColId = 0L;
    		LONG	lo_string40ColId = 0L;
    		LONG	lo_string80ColId = 0L;
    
            LONG lColCount = aaApi_SelectColumnsByTable( lTableId );
            if (lColCount==0)
            {
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+2, 
    								L"Could not get column info.", 
    								L"Table appears to have no columns!");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+2;
            }
            else if (lColCount==-1)
            {
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+3, 
    								L"Could not get column info.", 
    								L"Problem with selecting column info using table id.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+3;
            }
    
            // sentinal value of zero...
            lo_string10ColId = 0L;
    		lo_string40ColId = 0L;
    		lo_string80ColId = 0L;
            for (LONG j=0; j<lColCount; j++)
            {
                if (_tcsicmp(_T("o_string10"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0)
                {
                    lo_string10ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j);
                }
                else if (_tcsicmp(_T("o_string40"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0)
                {
                    lo_string40ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j);
                }
                else if (_tcsicmp(_T("o_string80"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0)
                {
                    lo_string80ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j);
                }
            }
    
    		/**************************************************************************
            Exercise 2, Step 4
    		If any of our column ids are zero, the environment didn't contain it, so
            report an error and return.
    		***************************************************************************/
            if ((lo_string10ColId==0) || (lo_string40ColId==0) || (lo_string80ColId==0))
            {
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+4, 
    								L"Could not determine column ids.", 
    								L"Environment table is missing one or more target columns.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+4;
            }
    
    		/**************************************************************************
            Exercise 2, Step 6
    		Now we have the information we need to create the new row, so now we 
    		need to initialize the "Insert" buffer.  
            Hint:  Use aaApi_FreeLinkDataInsertDesc() - read the documentation
    		***************************************************************************/
            aaApi_FreeLinkDataInsertDesc();
    
    		/**************************************************************************
            Exercise 2, Step 7
    		Next we populate the insert buffer with the data that we want to write.  You 
    		must always change the data into a string and account for the correct format
    		if using dates.  For this exercise we are only updating string data.  
            
            Hints: Use aaApi_SetLinkDataColumnValue().  Place any text you like into each 
    		user attribute.  
    		***************************************************************************/
            if (!aaApi_SetLinkDataColumnValue (lTableId, lo_string10ColId, _T("via API" )))
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+5, 
    								L"Could not populate buffer for o_string10.", 
    								L"In the real world, we would also want to know the PW error here.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+5;
    		}
            if (!aaApi_SetLinkDataColumnValue (lTableId, lo_string40ColId, aaApi_GetOSUserName()))
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+6, 
    								L"Could not populate buffer for o_string40.", 
    								L"In the real world, we would also want to know the PW error here.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+6;
    		}
            if (!aaApi_SetLinkDataColumnValue (lTableId, lo_string80ColId, aaApi_GetHostName()))
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+7, 
    								L"Could not populate buffer for o_string80.", 
    								L"In the real world, we would also want to know the PW error here.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+7;
    		}
    
    		/**************************************************************************
            Exercise 2, Step 8
    		Now we create the row.  To do this we need to populate a structure that we
    		then pass via aaApi_CreateEnvAttr().  
    
            Hints: Use aaApi_CreateEnvAttr() and read the documentation.  Make sure that
            reset the structure's contents each time you use it.
    		***************************************************************************/
            // solution using aaApi_CreateLinkDataAndLink()
            WCHAR szColAttrValue[256] = {0};
            memset (szColAttrValue, '\0',sizeof (szColAttrValue)/sizeof(WCHAR));
    
            if(!aaApi_CreateLinkDataAndLink (lTableId, AADMS_EALNK_DOCUMENT, *(pProjects+i), *(pDocuments+i), 
                &lAttrColId, szColAttrValue, sizeof (szColAttrValue)/sizeof(WCHAR)))
            {
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+8, 
    								L"Could not create attribute row.", 
    								L"In the real world, we would also want to know the PW error here.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+8;
            }
    /*
            // alternate solution using aaApi_CreateEnvAtt()
            LONG lAttrNo = 0L;
            AAEALINKAGE linkage;        // check the documentation
    
            lAttrNo = 0;                // always set this to zero before you pass it
            memset(&linkage, 0, sizeof(linkage));
            linkage.lLinkageType = AADMS_EALNK_DOCUMENT;
            linkage.documentId.lProjectId = *(pProjects+i);
            linkage.documentId.lDocumentId = *(pDocuments+i);
    
            if (!aaApi_CreateEnvAttr (lTableId, &linkage, &lAttrNo))
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+8, 
    								L"Could not create attribute row.", 
    								L"In the real world, we would also want to know the PW error here.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+8;
    		}
    */
    	} // for each document
    
    	return 0;  // all documents were successfully processed!
    }
    
    /******************************************************************************
    Exercise 1, Step 4
    Set a breakpoint at the beginning of this function.
    *******************************************************************************/
    extern "C" int WINAPI DocCmd_UpdateAttributes
    (
    unsigned int count,
    long*  pProjects,
    long*  pDocuments
    )
    {
    	AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
        /******************************************************************************
        Exercise 3, Step 1
    	Create a loop to process each document.  In the real world, you would want
    	to give the user a way to cancel the loop if you show each error, or a way
    	to process all of the documents and review the errors later.  We won't do 
    	that in this exercise.  Just show errors as you go and don't worry about
    	giving the user a way to cancel the processing, just stop on the first error.
    
        Hints:  Use aaApi_SetLastError() and aaApi_ShowLastError() for custom error
        messages.  To make your error codes unique from ProjectWise, you should use 
        AAERR_USERERROR_FIRST as your starting point for your custom error codes.
    	******************************************************************************/
    	for (LONG i=0; i<(LONG)count; i++)
    	{
            /******************************************************************************
            Exercise 3, Step 2
    		We need some information about the environment.  
            
            Hint: We can get two key items that we need from the document's folder by using 
            aaApi_GetEnvTableinfoByProject().  
    		******************************************************************************/
            LONG	lEnvId = 0L;
            LONG	lTableId = 0L;
            LONG	lAttrColId = 0L;    // the column id that holds the attribute id's value
    
            if (!aaApi_GetEnvTableInfoByProject (*(pProjects+i), &lEnvId, &lTableId, &lAttrColId))
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+11, 
    								L"Could not get environment info.", 
    								L"Problem with selecting environment info using project id.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+11;
    		}
    
    		/**************************************************************************
            Exercise 3, Step 3
    		We need to determine the column ids that we will need for our values. Since
            the documents passed to use could be from different folders with different
            environments, we will look these up for each document to keep things simple.
    
            Hints: Use aaApi_SelectColumnsByTable(), then get the names of each using
            aaApi_GetColumnStringProperty() and when you find a match, use
            aaApi_GetColumnNumericProperty() and save that column id to a local variable
            with a meaninful name like LONG lo_string10ColId.  We will need it later.
            Make sure you add error handling, but keep it simple by displaying your own
            error message and then returning from the function.
    		***************************************************************************/
    		LONG	lo_string10ColId = 0L;
    		LONG	lo_string40ColId = 0L;
    
            LONG lColCount = aaApi_SelectColumnsByTable( lTableId );
            if (lColCount==0)
            {
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+12, 
    								L"Could not get column info.", 
    								L"Table appears to have no columns!");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+12;
            }
            else if (lColCount==-1)
            {
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+13, 
    								L"Could not get column info.", 
    								L"Problem with selecting column info using table id.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+13;
            }
    
            // sentinal value of zero...
            lo_string10ColId = 0L;
    		lo_string40ColId = 0L;
            for (LONG j=0; j<lColCount; j++)
            {
                if (_tcsicmp(_T("o_string10"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0)
                {
                    lo_string10ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j);
                }
                else if (_tcsicmp(_T("o_string40"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0)
                {
                    lo_string40ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j);
                }
            }
    
    		/**************************************************************************
            Exercise 3, Step 4
    		If any of our column ids are zero, the environment didn't contain it, so
            report an error and return.
    		***************************************************************************/
            if ((lo_string10ColId==0) || (lo_string40ColId==0))
            {
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+14, 
    								L"Could not determine column ids.", 
    								L"Environment table is missing one or more target columns.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+14;
            }
    
    		/**************************************************************************
            Exercise 3, Step 6
    		Now we have the information we need to update any existing rows for the 
            current document, so we will need to create a loop to process all the 
            existing rows.  
            
            Hints: Use aaApi_SelectLinks() and if there are no rows to process, just 
            skip the document.  If there is an error, report it and return.
    		***************************************************************************/
            LONG lLinkRows = aaApi_SelectLinks(*(pProjects+i), *(pDocuments+i));
    		if (-1==lLinkRows)
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+15, 
    								L"Could not select document sheets.", 
    								L"In the real world, we would also want to know the PW error here.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+15;
    		}
    		else if (0==lLinkRows)
    		{
    			// no rows to update - skip this document...
    			continue;
    		}
    
    			/**************************************************************************
                Exercise 3, Step 7
    			Create a loop to process each sheet.
                
                Hint:  You can just uncomment the for loop that is provided.
    			***************************************************************************/
    		for (LONG k=0; k<lLinkRows; k++)
    		{
    			/**************************************************************************
                Exercise 3, Step 8
    			For each existing row, we first need to initialize the "Update" buffer.  
                
                Hint:  Use aaApi_FreeLinkDataUpdateDesc() - read the documentation
    			***************************************************************************/
                aaApi_FreeLinkDataUpdateDesc();
    
    			/**************************************************************************
                Exercise 3, Step 9
    			Next we populate the update buffer with the data that we want to write.  You 
    			must always change the data into a string and account for the correct format
    			if using dates.  For this exercise we are only updating string data.
                
                Hints:  Use aaApi_UpdateLinkDataColumnValue().  Place any text you like into 
                each user attribute.  Make it obvious that the value has changed.
    			***************************************************************************/
    			if (!aaApi_UpdateLinkDataColumnValue (lTableId, lo_string10ColId, _T("Updated!")))
    			{
    				aaApi_SetLastError (AAERR_USERERROR_FIRST+16, 
    									L"Could not populate update buffer for o_string10.", 
    									L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError ();
    				return AAERR_USERERROR_FIRST+16;
    			}
                WCHAR   szDSName[41];
                memset (szDSName,'\0', sizeof(szDSName)/sizeof(WCHAR));
                aaApi_GetActiveDatasourceName(szDSName,sizeof(szDSName)/sizeof(WCHAR));
    
    			if (!aaApi_UpdateLinkDataColumnValue (lTableId, lo_string40ColId, szDSName))
    			{
    				aaApi_SetLastError (AAERR_USERERROR_FIRST+17, 
    									L"Could not populate update buffer for o_string40.", 
    									L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError ();
    				return AAERR_USERERROR_FIRST+17;
    			}
    
    			/**************************************************************************
                Exercise 3, Step 10 - "part 1"
    			To update the existing row, we need to know the value of the
    			a_attrno column.  We can get that value with
    			aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, k).  
    			***************************************************************************/
    			LPCWSTR lpcwstrColumnValue = aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, k);
    
    			/**************************************************************************
                Exercise 3, Step 10 - "part 2"
    			Now we update the row.  We can do it with 
    			aaApi_UpdateLinkData() or aaApi_UpdateEnvAttr()
    			***************************************************************************/
                // solution using aaApi_UpdateLinkData()
                if (!aaApi_UpdateLinkData(lTableId, lAttrColId, lpcwstrColumnValue))
    			{
    				aaApi_SetLastError (AAERR_USERERROR_FIRST+18, 
    									L"Could not update attribute row.", 
    									L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError ();
    				return AAERR_USERERROR_FIRST+18;
    			}
    /*            
                // alternate solution using aaApi_UpdateEnvAttr()
                LONG lAttrRecId = _wtol(lpcwstrColumnValue);
                if (!aaApi_UpdateEnvAttr (lTableId, lAttrRecId))
                {
    				aaApi_SetLastError (AAERR_USERERROR_FIRST+18, 
    									L"Could not update attribute row.", 
    									L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError ();
    				return AAERR_USERERROR_FIRST+18;
                }
    */
    		} // for each sheet
    	} // for each document
    
    	return 0;  // all documents were successfully processed!
    }
    
    /******************************************************************************
    Exercise 1, Step 4
    Set a breakpoint at the beginning of this function.
    *******************************************************************************/
    extern "C" int WINAPI DocCmd_DeleteExtraSheets
    (
    unsigned int count,
    long*  pProjects,
    long*  pDocuments
    )
    {
    	AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
        /******************************************************************************
        Exercise 4, Step 1
    	Create a loop to process each document.  In the real world, you would want
    	to give the user a way to cancel the loop if you show each error, or a way
    	to process all of the documents and review the errors later.  We won't do 
    	that in this exercise.  Just show errors as you go and don't worry about
    	giving the user a way to cancel the processing, just stop on the first error.
    
        Hints:  Use aaApi_SetLastError() and aaApi_ShowLastError() for custom error
        messages.  To make your error codes unique from ProjectWise, you should use 
        AAERR_USERERROR_FIRST as your starting point for your custom error codes.
    	******************************************************************************/
    	for (LONG i=0; i<(LONG)count; i++)
    	{
            /******************************************************************************
            Exercise 4, Step 2
    		We need some information about the environment.  
            
            Hint: We can get two key items that we need from the document's folder by using 
            aaApi_GetEnvTableinfoByProject().  
    		******************************************************************************/
            LONG	lEnvId = 0L;
            LONG	lTableId = 0L;
            LONG	lAttrColId = 0L;
    
            if (!aaApi_GetEnvTableInfoByProject (*(pProjects+i), &lEnvId, &lTableId, &lAttrColId))
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+21, 
    								L"Could not get environment info.", 
    								L"Problem with selecting environment info using project id.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+21;
    		}
    
    		/**************************************************************************
            Exercise 4, Step 3
    		Now we have the information we need to delete any existing rows for the 
            current document, so we will need to create a loop to process all the 
            existing rows.  
            
            Hints: Use aaApi_SelectLinks() and if there are 0 or 1 row to process, just 
            skip the document.  If there is an error, report it and return.
    		***************************************************************************/
            LONG lLinkRows = aaApi_SelectLinks(*(pProjects+i), *(pDocuments+i));
    		if (-1==lLinkRows)
    		{
    			aaApi_SetLastError (AAERR_USERERROR_FIRST+22, 
    								L"Could not select document sheets for delete.", 
    								L"In the real world, we would also want to know the PW error here.");
    			aaApi_ShowLastError ();
    			return AAERR_USERERROR_FIRST+22;
    		}
    		else if (lLinkRows<2)
    		{
    			// no rows to delete - skip this document...
    			continue;
    		}
    
    		/**************************************************************************
            Exercise 4, Step 4
    		For each "extra" existing row, delete it!
            
            Hint:  Use aaApi_DeleteEnvAttr() or aaApi_DeleteLinkData() - read the 
            documentation.
    		***************************************************************************/
    		for (LONG k=1; k<lLinkRows; k++)
    		{
                // solution using aaApi_DeleteLinkData()
                if (!aaApi_DeleteLinkData (*(pProjects+i), *(pDocuments+i), lTableId, lAttrColId, 
                        aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, k)))
                {
    				aaApi_SetLastError (AAERR_USERERROR_FIRST+23, 
    									L"Could not delete extra sheet.", 
    									L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError ();
    				return AAERR_USERERROR_FIRST+23;
                }
    /*
                // alternative solution using aaApi_DeleteEnvAttr()
                LONG lAttrRecId = 0;
    
                lAttrRecId = _wtol(aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, k));
    			if (!aaApi_DeleteEnvAttr (lTableId, lAttrRecId))
    			{
    				aaApi_SetLastError (AAERR_USERERROR_FIRST+23, 
    									L"Could not delete extra sheet.", 
    									L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError ();
    				return AAERR_USERERROR_FIRST+23;
    			}
    */
    		} // for each sheet
    	} // for each document
    
    	return 0;  // all documents were successfully processed!
    }
    

    This code provides menu items for ProjectWise Explore that create, update and delete attribute rows (i.e ProjectWise "sheets"). You probably will be able to gleem out what you need from this code.  Take a look at the documentation for each of the functions in the code and eventually you will figure it out, but it will probably take you a while as this topic is about 1/2 day of classroom time!

    HTHs

  • Hi,

    Thank you for so informative answer.

    Let me explain me more about the task. We have our working PW environment where we generating new files with wizard. Our client ask upload some our files to they PW working environment as well. My task is to automate this process.

    I need create file in our PW environment and upload as new file to client PW.

    When I using file creation wizard I fill out required fields, but I cant do this again when I create this file on client PW using API.

    What did you think is the optimal way to do this. I already have all values, I just need have the same on other PW.

    Thank you.

    Darius

  • Darius,

    "Words are getting in the way" again.  I don't really understand what you need/want to do.  There are multiple options for importing files into ProjectWise.  Files are just a "property" of a document, so have you considered creating "abstract" or "place holder" documents in your datasource, and then adding the files later in your process?  You can also reserve document codes in advance of using them, perhaps that might make it easier to determine which code to use for a specific document/file.

    You can also "bulk load" files into ProjectWise and their documents in various ways.  You might consider the tools delivered with ProjectWise Explorer, or perhaps consider using the PowerShell module that is available.

    As for "optimal way", well that "depends" upon the details, so I suspect that it would take some study to find what appears to be the most optimal way, at least until new requirements are discovered, or the requirements change over time.

    A "common" solution for automatically importing files into a ProjectWise datasource is to create some sort of queue where the file and its metadata is provided by some means (file naming conventions, with metadata in an XML file, database entries, etc.) and then have a service process that queue in some organized manner.

    Perhaps other ProjectWise users/admins/developers might offer some suggestions on how they have solved similar needs.

    HTHs

  • My goal is to create absolutely automated process upload files from one PW to another and from Vault to PW.

    Idea is create file in one PW and do synchronization to another PW. I plan set scheduler which run some separate windows app, it finds files in one PW with specific property and upload/update file in other PW.

    Best regards

    Darius