GQL Modules - GraphQL Modules for Remote Client Browsers

Hi @QMcKay Hope you are well
i am getting this error please help

#Order Tag Free Tagging



Need another Special Report for this because unfortunately neither of the GraphQL Order Tag Queries/Mutations supply information about an Order Tag Group being configured for Free Tagging

getOrderTagGroups()
getOrderTagsForTerminalTicketOrder()

##GQLM Order Tag Groups (Special Report)

[Order Tag Groups:1,4,4,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1]
>>tagGroupId|tagGroupName|FreeTagging|SaveFreeTags|ButtonColor|MaxSelectedItems|MinSelectedItems|TaxFree|Hidden|Prefixes|AddTagPriceToOrderPrice|ColumnCount|ButtonHeight|FontSize|GroupTag|CategoryTags|FreeTagFormat|TicketDisplayFormat|SortOrder
{REPORT SQL DETAILS:
SELECT
 [Id] as [tagGroupId]
,[Name] as [tagGroupName]
,[FreeTagging]
,[SaveFreeTags]
,[ButtonColor]
,[MaxSelectedItems]
,[MinSelectedItems]
,[TaxFree]
,[Hidden]
,[Prefixes]
,[AddTagPriceToOrderPrice]
,[ColumnCount]
,[ButtonHeight]
,[FontSize]
,[GroupTag]
,[CategoryTags]
,[FreeTagFormat]
,[TicketDisplayFormat]
,[SortOrder]
FROM [OrderTagGroups]
ORDER BY [SortOrder], [Name]
:F.tagGroupId,F.tagGroupName,F.FreeTagging,F.SaveFreeTags,F.ButtonColor
,F.MaxSelectedItems,F.MinSelectedItems,F.TaxFree,F.Hidden,F.Prefixes
,F.AddTagPriceToOrderPrice,F.ColumnCount,F.ButtonHeight,F.FontSize,F.GroupTag,F.CategoryTags
,F.FreeTagFormat,F.TicketDisplayFormat,F.SortOrder
}
1 Like

The bad request has something to do with authorizing user.

It could be a few things. But if you are using chrome can you please press f12 and take screenshot and paste it first.

Woah didnt realize the free tagging required soo many sections for the special report. This is amazing stuff. But i fo have one question. Free tagging is working great! But what about the “save free tagging” option once its clicked. Would it still save the tag and leave it there or not? If we can make that work we could probably even create a “”-"" for the save free tag group. This way a user wouldnt have to keep typing but just highlight and select like pre-defined ones, then if there are too many on screen a user could remove the tag completely by highlighting a tag and press the minus(-) sign. What do you think?

It doesn’t. We just need the Tag Name and the FreeTagging column. But we might as well pull all data for future considerations.

SambaPOS will handle saving the Tags automatically if the TagGroup is configured for Save Free Tags.

In the video, the “Special Instructions” TagGroup is not configured to Save Free Tags, so the Tags are not actually saved. But the Order is being Tagged, so when you “close” the Order and “re-open” the Order, the Tags remain. When you click on a selected Free Tag, it unTags the Order, so that Free Tag disappears. The same would be true if you added another Order to the Ticket… the Free Tags from a previous Order would not be there, because they are not being saved in this particular case.

In regard to having a TagGroup configured to Save Free Tags, I can’t think of a way to delete saved Tags once they have been added to SambaPOS, other than via configuration in SambaPOS. I mean, it could be done with SQL, but we would need PHP for that.

Maybe that’s not what you mean?

I have another idea but not sure how to go about it.

We have config_modules.js which allows us to specify which Modules are available in the system, which is great, but I was thinking about taking it a step further and somehow limit Module access based on the Operator (the User Logged in via PIN).

Not sure how to go about this though. I do have a Special Report that pulls User details, but it would be fantastic to be able to set Custom Fields on a User like we can with Entities because the permissions system in SambaPOS does not align with the GQL Modules except for something like Reports …

SELECT
 u.[Id] as [uId]
,u.[Name] as [uName]
,CONVERT(VARCHAR(max),HASHBYTES('SHA2_512',convert(varchar(32),u.[PinCode])),2) as [uPin]
,CONVERT(VARCHAR(max),HASHBYTES('SHA2_512',convert(varchar(32),u.[Password])),2) as [uPassword]
,ur.[Id] as [urId]
,ur.[Name] as [urName]
,ur.[IsAdmin]
,ur.[DepartmentId] as [urDepartmentId]
,d.[Name] as [urDepartmentName]
,ur.[ManagementCommands]
,p.[Name] as [urPermissionName]
,p.[Value] as [urPermissionValue]
FROM [Users] u
join [UserRoles] ur on ur.[Id]=u.[UserRole_Id]
join [Departments] d on d.[Id]=ur.[DepartmentId]
left join [Permissions] p on p.[UserRoleId] = u.[UserRole_Id]
ORDER BY u.[Name]

I can add the code for Department switching and I am going to put the functional pieces in the Terminal/User screen …


But basically, you will have a function for setDepartment('departmentName') (or something like that) which you will be able fire from wherever you like, for example, using some button somewhere on the POS Module Screen.


##GQLM Departments (Special Report)

[Departments:1,2,2,1,2,1,1,1,1,1,1]
>>id|name|priceTag|warehouseId|warehouseName|ticketTypeId|ticketTypeName|menuId|menuName|ticketCreationMethod|SortOrder
{REPORT SQL DETAILS:
SELECT
d.[Id] as [id]
,d.[Name] as [name]
,isnull(d.[PriceTag],'REG') as [priceTag]
,d.[WarehouseId] as [warehouseId]
,w.[Name] as [warehouseName]
,d.[TicketTypeId] as [ticketTypeId]
,tt.[Name] as [ticketTypeName]
,d.[ScreenMenuId] as [menuId]
,m.[Name] as [menuName]
,CASE d.[TicketCreationMethod]
 WHEN 0 THEN 'Select Entity'
 ELSE 'Create Ticket'
 END as [ticketCreationMethod]
,d.[SortOrder]
FROM [Departments] d
LEFT JOIN [Warehouses] w on w.[Id]=d.[WarehouseId]
LEFT JOIN [TicketTypes] tt on tt.[Id]=d.[TicketTypeId]
LEFT JOIN [ScreenMenus] m on m.[Id]=d.[ScreenMenuId]
ORDER BY d.[SortOrder], d.[Name]
:F.id,F.name,F.priceTag,F.warehouseId,F.warehouseName,F.ticketTypeId,F.ticketTypeName
,F.menuId,F.menuName,F.ticketCreationMethod,F.SortOrder
}

The pin is coorect but can’t login.

Did you try restarting the Message Service?

Yes, I did but none of the pins work.

Find the function for validateUser. It is probably in sputils.js so do a search for it there. It should look something like this:

auth.validateUser = function (user, pin, callback) {
    spu.consoleLog('USER Validating by PIN ...');

    var usr = !clientSetting('userName') || clientSetting('userName')=='' || clientSetting('userName')=='undefined' || typeof clientSetting('userName')==='undefined' ? defaultUser : clientSetting('userName');

    var pwcookie = clientSetting('userPW');

    var pw  = !pwcookie || pwcookie=='' || pwcookie=='undefined' || typeof pwcookie==='undefined' ? defaultUserPassword : clientSetting('userPIN');

    var validated = false;

    if (pw=='') {

        //toggleLoginDisplay('show','pin');

        pw = document.getElementById('USER_inPIN').value;
        document.getElementById('USER_inPIN').value = '';

        usr = document.getElementById('USER_inNAME').value;
        document.getElementById('USER_inNAME').value = '';

        gql.EXEC(gql.getUser(pw), function(usrresp){
            if (usrresp.data && usrresp.data.user.name != '*') {
                usr = usrresp.data.user.name;

...

The error is likely coming from this line:

 if (usrresp.data && usrresp.data.user.name != '*') {

Post the code in that you find in that function. I likely updated the code like the above to fix that error, and you don’t have a late enough version (I have not updated Git in some time).

spu.validateUser = function (user, pin, callback) {
    spu.consoleLog('USER Validating by PIN ...');

	var usr = !clientSetting('userName') || clientSetting('userName')=='' || clientSetting('userName')=='undefined' || typeof clientSetting('userName')==='undefined' ? defaultUser : clientSetting('userName');

	var pwcookie = clientSetting('userPW');

	var pw  = !pwcookie || pwcookie=='' || pwcookie=='undefined' || typeof pwcookie==='undefined' ? defaultUserPassword : clientSetting('userPIN');

	var validated = false;

	if (pw=='') {

		//toggleLoginDisplay('show','pin');

		pw = document.getElementById('USER_inPIN').value;
		document.getElementById('USER_inPIN').value = '';

		usr = document.getElementById('USER_inNAME').value;
		document.getElementById('USER_inNAME').value = '';

		gql.EXEC(gql.getUser(pw), function(usrresp){
			if (usrresp.data && usrresp.data.user.name != '*') {
				usr = usrresp.data.user.name;
				
				spu.consoleLog('USER Validated: '+usr);
				toggleLoginDisplay('hide');
				validated = true;

				setCurrentUserData(usr,pw,validated, function d(ud){

Ok that looks fine.

Go into the Network tab of Dev Tools and find the graphql request and show the Response data …

Hmm I don’t have “Response Payload”, only “Request Header”

Yes you do.

Look at my screenshot again.

First you need to find the proper request for the graphql getUser() post, which you can identify in the Headers tab.

Once you find the correct request, switch to the Response tab to see what is there. I will guess the reponse contains an error.

@sukasem, I should have trapped that error like I do with most things, but for some reason I did not. You can update the validateUser() code with this:

spu.validateUser = function (user, pin, callback) {
    var fn = spu.fi(arguments);

    spu.consoleLog('USER Validating by PIN ...');

    var usr = !clientSetting('userName') || clientSetting('userName')=='' || clientSetting('userName')=='undefined' || typeof clientSetting('userName')==='undefined' ? defaultUser : clientSetting('userName');

    var pwcookie = clientSetting('userPW');

    var pw  = !pwcookie || pwcookie=='' || pwcookie=='undefined' || typeof pwcookie==='undefined' ? defaultUserPassword : clientSetting('userPIN');

    var validated = false;

    if (pw=='') {

        //toggleLoginDisplay('show','pin');

        pw = document.getElementById('USER_inPIN').value;
        document.getElementById('USER_inPIN').value = '';

        usr = document.getElementById('USER_inNAME').value;
        document.getElementById('USER_inNAME').value = '';

        gql.EXEC(gql.getUser(pw), function(response){
            if (response.errors) {
                gql.handleError(fn+' gql.getUser',response);
            } else {
                if (response.data && response.data.user && response.data.user.name != '*') {
                    usr = response.data.user.name;

                    spu.consoleLog('USER Validated: '+usr);
                    toggleLoginDisplay('hide');
                    validated = true;

                    setCurrentUserData(usr,pw,validated, function d(ud){

                        if (userLogout) {
                            // registerTerminal(terminal,department,ticketType,user,reRegister)
                            registerTerminal(POS_Terminal.name,departmentName,ticketTypeName,currentUser,true, function rereg(data) {
                                var term = data;
                                if (term.registered) {
                                    spu.consoleLog('TERMINAL Re-registered ['+term.name+'] with Validated User ['+currentUser+']: '+term.id);
                                    userLogout = false;
                                    navigateTo('module',module,module);
                                    if (callback) {
                                        callback(ud);
                                    }
                                }
                            });
                        } else {
                            navigateTo('module',module,module);
                        }

                        if (callback) {
                            callback(ud);
                        }

                    });

                } else {
                    // fail
                    spu.consoleLog('USER Validation by PIN FAILED: '+pw);
                    $('#USER_Auth_Message').html('USER Validation by PIN FAILED !!!');

                    toggleLoginDisplay('show','pin');

                    clientSetting('userPW','','del');
                    pw = '';
                    userLogout = true;

                    setCurrentUserData(usr,pw,validated, function d(ud){

                        if (callback) {
                            callback(currentUserData);
                        }

                    });
                }
            }
        });

    } else {

        spu.consoleLog('USER Validating from Cache ...');

        $('#USER_Auth_Message').html('USER Validating from Cache ...');
        
        usr = clientSetting('userName');
        pw  = clientSetting('userPW');

        gql.EXEC(gql.getUser(pw), function(usrresp){
            if (usrresp.data && usrresp.data.user.name != '*') {
                usr = usrresp.data.user.name;

                spu.consoleLog('USER Validated from Cache: '+usr);
                toggleLoginDisplay('hide');
                validated = true;

                setCurrentUserData(usr,pw,validated, function d(ud){

                    if (userLogout) {
                        // registerTerminal(terminal,department,ticketType,user,reRegister)
                        registerTerminal(POS_Terminal.name,departmentName,ticketTypeName,currentUser,true, function rereg(data) {
                            var term = data;
                            if (term.registered) {
                                spu.consoleLog('TERMINAL Re-registered ['+term.name+'] with Validated User ['+currentUser+']: '+term.id);
                                userLogout = false;
                                navigateTo('module',module,module);
                                if (callback) {
                                    callback(currentUserData);
                                }
                            }
                        });
                    } else {
                        navigateTo('module',module,module);
                    }

                    if (callback) {
                        callback(currentUserData);
                    }

                });

            } else {
                // fail
                spu.consoleLog('USER Validation from Cache FAILED: '+usr);
                $('#USER_Auth_Message').html('USER Validation from Cache FAILED !!!');

                toggleLoginDisplay('show','pin');

                clientSetting('userPW','','del');
                pw = '';
                userLogout = true;

                setCurrentUserData(usr,pw,validated, function d(ud){

                    if (callback) {
                        callback(currentUserData);
                    }

                });
            }
        });

    }

};

##Department Switching

I made changes to the registerTerminal() function for this to work, and some changes in POS_refreshPOSDisplay() so that it pulls the correct Menu, and looks for Ticket Creation Method to automatically pull up Entity Select.

The BAR Department uses Select Entity Ticket Creation Method, and it uses Menu2 which is a smaller Menu than for my Restaurant Department.


When I click Response Tab. The code is html page, not response data.

<!doctype html>
<html lang="en">
<head>
<title>SambaPOS Mobile Application ~ SambaPOS</title>

<meta name="description" content="SambaPOS Mobile Application ~ SambaPOS">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<meta charset="utf-8" />
<meta name="Keywords" content="flarfernugen">
<meta name="GENERATOR" content="NOTEPAD">
<meta name="robots" content="no index, no follow">

<link rel="stylesheet" type="text/css" href="zcss/reset.min.css" />
<link rel="stylesheet" type="text/css" href="zcss/sambapos.css" media="screen" />

<link rel="shortcut icon" href="images/icons/favicon.ico" />
<link rel="icon" type="image/gif" href="images/icons/favicon-blue.png" />



<!-- <script type="text/javascript" src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js'></script> -->
<script type="text/javascript" src='zjs/lib/jquery.min.js'></script>

<script type="text/javascript" src='zjs/lib/moment.min.js'></script>

<!-- <script src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/md5.js"></script> -->
<script type="text/javascript" src="zjs/lib/sha512.js"></script>

<!-- <script type="text/javascript" src='http://ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.2.0.min.js'></script> -->
<script type="text/javascript" src='zjs/lib/jquery.signalr.min.js'></script>

<script type="text/javascript" src='zconfigs/config.js'></script>
<script type="text/javascript" src='zconfigs/config_auth.js'></script>
<script type="text/javascript" src='zconfigs/config_modules.js'></script>
<script type="text/javascript" src='zjs/globalvars.js'></script>
<script type="text/javascript" src='zjs/sputils.js'></script>
<script type="text/javascript" src='zjs/gqlqueries.js'></script>
<script type="text/javascript" src='zjs/core.js'></script>

<script type="text/javascript" id="js_mod"></script>
</head>

<body>

    <div id="containerMaster">
        
        <div id="top">
            <div id="module" onClick="navigateTo('module','main_menu','main_menu');return false;" title="Click here for Main Menu">???</div>
            <div id="indicators" onClick="chatShowFull();return false;">
                <div id="connection" title="Connection Status: GREEN=Connected">...</div>
                <div id="workperiod" title="Workperiod ID/Status: GREEN=Open">...</div>
                <div id="battery" title="Battery Level">...</div>
                <div id="MSG_messaging" title="Click here to go to Fullscreen Messaging." onClick="chatShowFull();return false;"><div class="MSG_Indicator MSG_OldMessage">MSG</div></div>
            </div>
            <div id="currentUser" title="Current Terminal and User.  Click to Logout" onClick="showTerminalInfo();return false;">[Terminal] (User)</div>
            <div id="traffic">
                <div id="signalRbytes" title="SignalR KB" onClick="clearTrafficBytes('signalr');return false;">0.00</div>
                <div id="GQLbytes" title="GraphQL KB sent/rcvd/TTL" onClick="clearTrafficBytes('gql');return false;">0.00/0.00/0.00</div>
            </div>
            <div id="clock_dateTime" title="Current Date/Time">
                <span id="clock_date"></span>
                <span id="clock_time">00:00:00 AM</span>
            </div>
        </div>
        
        <!-- this section loaded by JQuery depending on nav var -->
        <div id="containerMODULE">
        </div> <!-- #containerMODULE -->

    </div><!-- #containerMaster -->

    <!-- hidden elements -->
    <div id="POS_EntityGrids"></div>
    <div id="POS_OrderTagDisplay" style="display:none">
        <div id="POS_OrderTagsHeader"></div>
        <div id="POS_OrderTags"></div>
        <!-- <div id="POS_OrderTagScreenCommands"></div> -->
        <div class="bottomShadow"></div>
    </div>
    
    <div id="infoMessage" title="click to close" style="display:none"></div>
    <div id="loadMessage" style="display:none"></div>
    <div id="errorMessage" style="display:none"></div>
    <div id="warningMessage" style="display:none"></div>
    <div id="helpMessage" style="display:none"></div>
    
    <div id="TERM_Info" style="display:none"></div>

    <div id="inputDialog" style="display:none">
        <div id="inputInfo"></div>
        <div id="inputLabel"></div>
        <div id="inputType"></div>
        <div style="display:flex;">
        <div id="inputGo" class="inputButton">OK</div>
        <div id="inputClear" class="inputButton">CLEAR</div>
        <div id="inputCancel" class="inputButton">CANCEL</div>
        </div>
    </div>


    <div id="USER_Auth" style="display:none">
		<div id="USER_Auth_Message">Enter your PIN</div>
		<input type="text" id="USER_inNAME" size="5" title="enter USERNAME and click LOGIN" placeholder="(User Name)">
		<br/>
		<input type="password" id="USER_inPIN" size="5" title="enter PIN and click LOGIN" placeholder="(pin)">
		<input type="password" id="USER_inPW" size="5" title="enter Password and click LOGIN" placeholder="(password)">
		<br/>
        <div id="numpad">
            <div class="numpad-button" onclick="enterdigit('1','USER_inPIN')">1</div>
            <div class="numpad-button" onclick="enterdigit('2','USER_inPIN')">2</div>
            <div class="numpad-button" onclick="enterdigit('3','USER_inPIN')">3</div>
            <br />
            <div class="numpad-button" onclick="enterdigit('4','USER_inPIN')">4</div>
            <div class="numpad-button" onclick="enterdigit('5','USER_inPIN')">5</div>
            <div class="numpad-button" onclick="enterdigit('6','USER_inPIN')">6</div>
            <br />
            <div class="numpad-button" onclick="enterdigit('7','USER_inPIN')">7</div>
            <div class="numpad-button" onclick="enterdigit('8','USER_inPIN')">8</div>
            <div class="numpad-button" onclick="enterdigit('9','USER_inPIN')">9</div>
            <br />
            <div class="numpad-button" style="background-color:#660000;" onclick="enterdigit('back','USER_inPIN');return false;">&lt;</div>
            <div class="numpad-button" onclick="enterdigit('0','USER_inPIN');return false;">0</div>
            <div class="numpad-button" style="background-color:#000000;" onclick="enterdigit('clear','USER_inPIN');return false;">X</div>
            <br />
        </div>
        <div id="USER_Login_PIN" class="numpad-button" style="background-color:#006600;" onclick="spu.validateUser();return false;">LOGIN</div>
        <div id="USER_Login_Password" class="numpad-button" style="background-color:#006600;" onclick="spu.authenticateApplication();return false;">LOGIN</div>
    </div>
    

    <div id="MSG_fullscreen" style="display:none">
        <div id="MSG_FS_area">
            <div id="MSG_FS_messages" title="Incoming and Outgoing Messages."></div><input id="MSG_FS_Input" name="MSG_FS_Input" type="text" placeholder="(type message)" title="Enter your Message here and hit ENTER or click SEND."/>
            <div id="MSG_FS_buttons">
                <div id="MSG_FS_Send" title="Click here to send your Message." onclick="chatSendClick('MSG_FS');return false;">SEND</div>
                <div id="MSG_FS_close" title="Click here to close Fullscreen Messaging." onClick="chatShowFull('hide');return false;">CLOSE</div>
            </div>
        </div>
    </div>


</body>

</html>

You have your Network Tag filtered to show only “Doc” which is HTML only.

You need to select “All” to see other requests. Or switch the Filter OFF.

You are looking for graphql posts/requests.

Ah got it sorry.
{
“data”: {},
“errors”: null
}

Here report User

Or un-hash

I enter pin 1234