GQL Modules - GraphQL Modules for Remote Client Browsers

#GQL Modules

A set of GraphQL “modules” that facilitate access to certain SambaPOS 5 functionality.

Works with any ES5-compliant Browser (iPhone, iPad, Android, Windows Tablets, etc).

Designed as a Single Page App (SPA) so that page refreshes/reloads are not necessary, since a reload will break the connection to the GraphQL/signalR/Message Server.

:warning: DISCLAIMER: I am going to release this code as-is with no guarantee of support whatsoever. However, I am also going to attempt to walk through any required setup.

Oh yah…

:warning: NOTE: You may find the code to be poorly written, and it contains very little in the way of comments to explain how it works.


##Setup

:bulb: UPDATE 2017-06-03 ::: Updates for GraphQL Authentication for SambaPOS 5.1.62+

:bulb: UPDATE 2016-11-23 ::: POS Module (“QMPOS”) enabled!

:bulb: UPDATE 2016-10-23 ::: PHP dependencies have been removed, so you no longer need a PHP-enabled site. All Modules function without PHP. However, the Modules require some “special” Custom Reports in SambaPOS in order to function.

  • Code Configuration ::: Basic information about the code, and things you need to configure in order for it to work with your system.

  • Special Reports ::: Since PHP code-removal, the Modules require a few “special” Custom Reports in SambaPOS. This section contains the Reports that you need to create.

  • HUB Automation ::: Common SambaPOS Automation elements that allow for certain Module inter-operation with SambaPOS.

##Embedding a Module in a SambaPOS HTML Viewer Widget

Even if you may not be interested in the CHAT Module, the method for embedding a Module in a SambaPOS HTML Viewer Widget is detailed in this section. It is quick & easy to do, so take a quick look…


###These Modules are available. Some of them require certain Actions and Rules be created within SambaPOS:

###These Modules are currently inoperable:


##The “CODE”

Yes, here it is … “the code”, hosted on gitHub …

https://github.com/QTMcKay/SambaPOS-GQLmodules

ISSUES: please use gitHub to report issues.
QUESTIONS: post your questions here in the Forum, on this Topic.


##DB Tools Import Files

Main Automation:
!GQLM_HUB_BUS_Automation.zip (3.4 KB)

Main Automation: (same as above but also with Customer Feedback display)
!GQLM_HUB_BUS_Feedback_Automation.zip (3.6 KB)

Special Reports:
!GQLM_SpecialReports.zip (2.8 KB)

Task Types:
!GQLM_TaskTypes.zip (565 Bytes)

Customer Display:
!GQLM_CustomerDisplay.zip (2.3 KB)

Kitchen Display:
!GQLM_KitchenDisplay_Automation_TaskTypes_Printing_Screens.zip (4.9 KB)

3 Likes

#Code Configuration

Directory structure:


###zconfigs\config.js###

This file contains the settings for your GQL/Message Server address and Port. The code contained in here tries to self-configure the GQL/Message Server, and should work properly in most cases, but you can override the values if necessary.

////////////////////////////////
//
// config
//
////////////////////////////////

//
// Some code requires a certain version of SambaPOS
// this will let the code decide how certain functions are called
// For example, GQL Authorization was added in .61+ so if the version
// set here is less than that, the Auth function will be bypassed
// However, if the version set here is less than that and you
// are running .61+, all GQL functions will fail,
// because GQL REQUIRES Auth in .61+
//
var SambaPOS = '5.1.62';

//
// If you have a PHP-enabed site, set this to true
// this will tell the code to try to use a Local IP lookup
// which is contained in the file: /zjs/lib/ipinfo.php
// Setting this to false will tell the code to use a Remote IP lookup service
// This applies to User Authorization Bypass by IP address
//
var PHP = true;


// derive Server information from Address Bar
var webHost  = location.hostname;  // myServer.com
var webPort  = location.port;      // blank assumes port 80
var webPath  = location.pathname;  // might be like /app/mysite/blah
var webParm  = location.search;    // things after '?', like ?module=customer_display
var webProto = location.protocol;  // usually http: or https:

var webUrl   = webProto + '//' + webHost + (location.port ? ':'+location.port : '') + webPath;

// Message Server
var msgsrv = webHost;

// GraphQL server
var GQLhost = msgsrv;
var GQLport = '9000'; // generally, this is the only parameter that might need to change
var GQLpath = '/api/graphql/';
var GQLurl  = webProto + '//' + GQLhost + ':' + GQLport + GQLpath;

// SIGNALR server
var SIGNALRhost = msgsrv;
var SIGNALRport = GQLport;
var SIGNALRpath = '/signalr';
var SIGNALRhubs = '/signalr/hubs/';
var SIGNALRurl  = webProto + '//' + SIGNALRhost + ':' + SIGNALRport + SIGNALRpath;
var SIGNALRhub  = webProto + '//' + SIGNALRhost + ':' + SIGNALRport + SIGNALRhubs;

...

###zconfigs\config_auth.js###

This configuration file contains variables that you need to set for GraphQL Authentication. In particular, you need to set the GQLclientId to match the Application Identifier that you configure in SambaPOS.

// this Application has access to ALL functions, INCLUDING Remote Connections
var GQLclientId = 'GQLModulesRemote'; // Client Id (Identifier) needs to match in SambaPOS Users > Application
var GQLsecret   = 'GQLMsecret';       // Secret Key (optional) when Application configured to use Secret Key
var GQLappUserName = '';              // UserName needs to match in SambaPOS Users > User List
var GQLappPassword = '';              // Password needs to match in SambaPOS Users > User List
var GQLdeviceId = '';

###zconfigs\config_modules.js###

This file allows you to configure which Modules are available. If you don’t want to provide access to certain modules, either comment out the line, or delete it. It also contains Module-specific settings that you might need to configure to match your SambaPOS Configuration.

////////////////////////////////
//
// config_modules
//
////////////////////////////////

// you can change the location of the Modules and set this variabe to match
var modulePath = 'modules/';

// control which Modules are available, and the order in which they appear on the Main Menu
var availableModules = [];
    availableModules.push('Customer Display');
    availableModules.push('Kitchen Display');
    availableModules.push('Ticket Explorer');
    availableModules.push('POS');
    availableModules.push('CHAT');
    availableModules.push('Timeclock');
//    availableModules.push('Time Clock');
    availableModules.push('Timeclock Policies');
//    availableModules.push('Punch Editor');
    availableModules.push('Task Editor');
    availableModules.push('Reports');

###zcss\sambapos.css###

The main Style Sheet used to control how everything looks.

/* Basic Style*/
* { margin:0; padding:0;}
html, body { height: 100%; width:100%; border-collapse:collapse; }

body {
    font-family:Tahoma,Verdana,Consolas;
    font-size:20px;
    color:#AA5555;
    background-color:#363636;
}
a,a.visited {
    color:#FF5500;
    text-decoration:none;
}
 ... etc ...

##Modules

Each Module has it’s own folder containing an index.html file, and a module.js file. The HTML file is pure HTML. These act as “templates” that are updated by Javascript. The JS file contains code that is applicable to that particular Module. To further secure access to only particular Modules, you can delete module folders so that the code for those modules does not exist. Here is an example of the Customer Display Module HTML file …

###customer_display\index.html###

        <div id="containerMain">
                <div id="CD_CustomerDisplay">
                    <div id="CD_ticketHeader"></div>
                    <div id="CD_ticketTotalValue"></div>
                    <div id="CD_ticketDiscounts"></div>
                    <div id="CD_orders"></div>
                    <div id="CD_idle"></div>
                </div>
        </div>

###zjs\core.js###

This file is where the BULK of the code is, and contains only Javascript. If you want to modify how the code works, this is where most of your edits will be done.

////////////////////////////////
//
// core
//
////////////////////////////////

function loadMODULE(modscreen) {
    
    spu.refreshMoments();
    
    if (inSambaPOS) {
        // if the page is running inside SambaPOS HTML Viewer Widget
        // get rid of Top and Bottom bars
        spu.hideHeader();                
    }

    //URLmodule = urlParm["module"];
    //URLmodule = (URLmodule ? URLmodule.toString().toLower() : '');

    spu.consoleLog('loadMODULE(modscreen) ... modscreen:"'+modscreen + '" URLmodule:"'+URLmodule + '" current mod:"'+module+'"');
    
    modscreen = (URLmodule ? URLmodule : modscreen);
    
    spu.consoleLog('loadMODULE(modscreen) ... modscreen:"'+modscreen + '" URLmodule:"'+URLmodule + '" current mod:"'+module+'"');

    module = '';
    for (var m=0; m<availableModules.length; m++) {
        var mod = availableModules[m].replace(/ /g,'_').toLowerCase();
        if (mod == modscreen) {
            module = modscreen;
            break;
        }
        
    }
    module = (modscreen=='main_menu' ? modscreen : module);
        

#Special Reports

Since the removal of all PHP code, the Modules require some “special” Custom Reports in SambaPOS.

:warning: You must create these Reports exactly as shown.


##GQLM Users

For User Authentication.

[Users:1,2,2,1,2,1]
>>id|name|PIN|roleId|role|isAdmin
{REPORT SQL DETAILS:
SELECT
 u.[Id] as [uId]
,u.[Name] as [uName]
,CONVERT(VARCHAR(max),HASHBYTES('SHA2_512',convert(varchar(32),u.[PinCode])),2) as [uPin]
,ur.[Id] as [urId]
,ur.[Name] as [urName]
,ur.[IsAdmin]
FROM [Users] u
left join [UserRoles] ur on ur.[Id]=u.[UserRole_Id]
ORDER BY u.[Name]
:F.uId,F.uName,F.uPin,F.urId,F.urName,F.IsAdmin}

##GQLM Custom Reports

Used by the Reports Module.

[Custom Reports:1,1,1,4,4,1,1,1,1]
>>id|reportType|displayInExplorer|name|template|pageSize|layouts|sortOrder|visualPrint
{REPORT SQL DETAILS:
SELECT
[Id]
,[ReportType]
,[DisplayInExplorer]
,[Name]
,convert(varchar(max),convert(varbinary(max),[Template]),2) as [Template]
,[PageSize]
,[Layouts]
,[SortOrder]
,[VisualPrint]
FROM [CustomReports]
WHERE [Name] not like 'GQLM%'
ORDER BY [SortOrder],[Name]
:F.Id,F.ReportType,F.DisplayInExplorer,F.Name,F.Template,F.PageSize,F.Layouts,F.SortOrder,F.VisualPrint}

##GQLM Task Types

Used by the Task Editor Module.

[Task Types:2,8]
>>id|name
{REPORT SQL DETAILS:
SELECT
 [Id]
,[Name]
FROM [TaskTypes]
ORDER BY [Name]
:F.Id,F.Name}

##GQLM Task Type Custom Fields

Used by the Task Editor Module.

[Task Type Custom Fields:1,1,4,4,2,2,2]
>>id|taskTypeId|taskType|name|fieldType|editingFormat|displayFormat
{REPORT SQL DETAILS:
SELECT
 cf.[Id]
,cf.[TaskTypeId]
,tt.[Name] as [TaskType]
,cf.[Name]
,CASE [FieldType]
 WHEN 0 THEN 'String'
 WHEN 1 THEN 'Number'
 WHEN 2 THEN 'Date'
 END as [FieldType]
,isnull([EditingFormat],'') as [EditingFormat]
,isnull([DisplayFormat],'') as [DisplayFormat]
FROM [TaskCustomFields] cf
JOIN [TaskTypes] tt on tt.[Id] = cf.[TaskTypeId]
ORDER BY tt.[Name], cf.[Id]
:F.Id,F.TaskTypeId,F.TaskType,F.Name,F.FieldType,F.EditingFormat,F.DisplayFormat}

##GQLM Terminals

Used by the POS Module.

##GQLM Terminals [0] (Report)##

Report Name: GQLM Terminals
Page Size:
Display in Report Explorer: unchecked
Visual Printing: unchecked

Template:

[Terminals:1,2]
>>dbId|name
{REPORT SQL DETAILS:
SELECT
 t.[Id] as [dbId]
,t.[Name] as [name]
FROM [Terminals] t
ORDER BY t.[Id]
:F.dbId,F.name}

##GQLM Ticket Type Entity Types

Used by the POS Module.

##GQLM Ticket Type Entity Types [0] (Report)##

Report Name: GQLM Ticket Type Entity Types
Page Size: 15cm
Display in Report Explorer: unchecked
Visual Printing: unchecked

Template:

[Ticket Type Entity Types:1,2,1,2,1,1,1,1]
>>id|name|ticketTypeId|ticketType|askBeforeCreatingTicket|state|copyToNewTickets|sortOrder
{REPORT SQL DETAILS:
SELECT
 [EntityTypeId] as [Id]
,[EntityTypeName] as [Name]
,[TicketTypeId] as [TicketTypeId]
,tt.[Name] as [TicketType]
,[AskBeforeCreatingTicket]
,[State]
,[CopyToNewTickets]
,et.[SortOrder]
--,et.[Id] as [dbId]
FROM [EntityTypeAssignments] et
JOIN [TicketTypes] tt on tt.[Id] = et.[TicketTypeId]
WHERE 1=1
AND tt.[Name] LIKE '$1'
ORDER BY tt.[SortOrder],et.[SortOrder],et.[EntityTypeName]

:F.Id,F.Name,F.TicketTypeId,F.TicketType,F.AskBeforeCreatingTicket,F.State,F.CopyToNewTickets,F.SortOrder}

##GQLM Automation Commands

Used by the POS Module.

[Automation Commands:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
>>id|name|buttonHeader|displayOnTicket|displayOnPayment|displayOnOrders|displayOnTicketList|displayUnderTicket|displayUnderTicket2|displayOnCommandSelector|displayOnNavigation|terminalId|terminal|departmentId|department|ticktTypeId|TicketType|roleId|role|enabledStates|visibleStates|fontSize|color|sortOrder|symbol|image|contentTemplate|navigationModule
{REPORT SQL DETAILS:
SELECT 
command.[Id]
,command.[Name]
,replace(replace(isnull([ButtonHeader],''),'\r','\\r'),char(13),'\\r') as [ButtonHeader]
,map.[DisplayOnTicket]
,map.[DisplayOnPayment]
,map.[DisplayOnOrders]
,map.[DisplayOnTicketList]
,map.[DisplayUnderTicket]
,map.[DisplayUnderTicket2]
,map.[DisplayOnCommandSelector]
,[DisplayOnNavigation]
,map.[TerminalId]
,isnull(term.[Name],'ANY') as [Terminal]
,map.[DepartmentId]
,isnull(dep.[Name],'ANY') as [Department]
,map.[TicketTypeId]
,isnull(tktType.[Name],'ANY') as [TicketType]
,map.[UserRoleId]
,isnull(ur.[Name],'ANY') as [UserRole]
,map.[EnabledStates]
,map.[VisibleStates]
,[FontSize]
,[Color]
,command.[SortOrder]
,[Symbol]
,[Image]
,[ContentTemplate]
,[NavigationModule]
FROM [AutomationCommands] command
JOIN [AutomationCommandMaps] map on map.[AutomationCommandId] = command.[Id]
LEFT JOIN [UserRoles] ur on ur.[Id] = map.[UserRoleId]
LEFT JOIN [Terminals] term on term.[Id] = map.[TerminalId]
LEFT JOIN [Departments] dep on dep.[Id] = map.[DepartmentId]
LEFT JOIN [TicketTypes] tktType on tktType.[Id] = map.[TicketTypeId]
WHERE 1=1
AND (   isnull(ur.[Name],'ANY') = 'ANY'
     OR isnull(ur.[Name],'ANY') = 'Admin'
    )
AND (   isnull(term.[Name],'ANY') != 'Server')
ORDER BY command.[SortOrder], command.[Name]
:F.Id,F.Name,F.ButtonHeader,F.DisplayOnTicket,F.DisplayOnPayment,F.DisplayOnOrders,F.DisplayOnTicketList,F.DisplayUnderTicket,F.DisplayUnderTicket2
,F.DisplayOnCommandSelector,F.DisplayOnNavigation,F.TerminalId,F.Terminal,F.DepartmentId,F.Department,F.F.TicketTypeId,F.TicketType
,F.UserRoleId,F.UserRole,F.EnabledStates,F.VisibleStates,F.FontSize,F.Color,F.SortOrder,F.Symbol,F.Image
,F.ContentTemplate,F.NavigationModule}
1 Like

#HUB Automation

The following Actions, Rules, Script and Reports are used in many of the JS Modules. There are not very of them, so whether or not you need them for a particular Module, you can go ahead and configure all of these.


#REPORT: Workperiod Status

This Report is used to check if a Workperiod is Open or Closed.

##Workperiod Status [0] (Report)##

Report Name: Workperiod Status
Page Size: 17cm
Display in Report Explorer: checked
Visual Printing: unchecked

Template:

[Workperiod Status:1,1, 4, 4,3,3]
>>Id|Open|Start|End|In|Out
{REPORT SQL DETAILS:SELECT TOP 10 [Id] as[WPID],CASE WHEN [StartDate]=[EndDate] THEN 'YES' ELSE 'No' END as [isOpen],left(convert(varchar(25),[StartDate],126),19) as [StartDate],left(convert(varchar(25),[EndDate],126),19) as [EndDate],[StartDescription],[EndDescription] FROM [WorkPeriods] ORDER BY [Id] DESC:F.WPID,F.isOpen,F.StartDate,F.EndDate,F.StartDescription,F.EndDescription}


#Scripts

##JSON Data [jsonData] (Script)##

Script Name: JSON Data
Script Handler: jsonData

Script:

function jsonParse(str, ret) {
  var hasJson = (str.indexOf('{') > -1 && str.indexOf('}') > -1? true : false);
  // we can't do JSON.parse if string does not contain JSON
  if (!hasJson) {
    return str;
  }
  
  var jsonObj = JSON.parse(str);
  
  var eventName  = (jsonObj["eventName"] ? jsonObj["eventName"] : '');
  var eventData  = (jsonObj["eventData"] ? jsonObj["eventData"] : '');
  var sid        = (jsonObj["sid"] ? jsonObj["sid"] : '');
  var userName   = (jsonObj["userName"] ? jsonObj["userName"] : '');
  var terminal   = (jsonObj["terminal"] ? jsonObj["terminal"] : '');
  var message    = (jsonObj["message"] ? jsonObj["message"] : '');
  
  if (ret=='eventName') {
    gql.Exec('mutation m {updateLocalSetting(name:"HUB_EVENT_NAME",value:"'+eventName+'"}');
    //dlg.AskQuestion('scrEv:'+eventName,'ok');
    return eventName;
  }
  if (ret=='eventData') {
    gql.Exec('mutation m {updateLocalSetting(name:"HUB_EVENT_DATA",value:"'+eventData+'"}');
    //dlg.AskQuestion('scrDa:'+eventData,'ok');
  	return eventData;
  }
  if (ret=='sid') {
    gql.Exec('mutation m {updateLocalSetting(name:"HUB_EVENT_SID",value:"'+sid+'"}');
    //dlg.AskQuestion('scrDa:'+data,'ok');
  	return sid;
  }
  if (ret=='userName') {
    gql.Exec('mutation m {updateLocalSetting(name:"HUB_EVENT_USERNAME",value:"'+userName+'"}');
    //dlg.AskQuestion('scrDa:'+userName,'ok');
  	return userName;
  }
  if (ret=='terminal') {
    gql.Exec('mutation m {updateLocalSetting(name:"HUB_EVENT_TERMINAL",value:"'+terminal+'"}');
    //dlg.AskQuestion('scrDa:'+terminal,'ok');
  	return terminal;
  }
  if (ret=='message') {
    gql.Exec('mutation m {updateLocalSetting(name:"HUB_EVENT_MESSAGE",value:"'+message+'"}');
    //dlg.AskQuestion('scrDa:'+message,'ok');
  	return message;
  }
  //dlg.ShowMessage('scrStr:'+str);
  return str;
}

#Actions

##HUB Broadcast Message [Broadcast Message] (Action)##

Action Name: HUB Broadcast Message
Action Type: Broadcast Message
###Parameters:###
Command: [:content]
Refresh Entity Screens: [:refreshEntityScreens]

##HUB ExecAMC [Execute Automation Command] (Action)##

Action Name: HUB ExecAMC
Action Type: Execute Automation Command
###Parameters:###
Automation Command Name: [:AMCname]
Command Value: [:AMCvalue]
Background: [:runInBG]
Delay: [:delay]

##HUB Store Setting Local [Update Program Setting] (Action)##

Action Name: HUB Store Setting Local
Action Type: Update Program Setting
###Parameters:###
Setting Name: [:settingName]
Setting Value: [:settingValue]
Update Type: Update
Is Local: True

##HUB Display Ticket [Display Ticket] (Action)##

Action Name: HUB Display Ticket
Action Type: Display Ticket
###Parameters:###
Ticket Id: [:ticketId]

##HUB Popup [Display Popup] (Action)##

Action Name: HUB Popup
Action Type: Display Popup
###Parameters:###
Name: [:name]
Title: [:title]
Message: [:msg]
Color: [:color]
Command Name: [:AMCname]
Command Value: [:AMCvalue]
Inactivity Timeout Seconds: [:timeoutSecs]

#Rules

##HUB Set Terminal and User - Clear [Application Started] (Rule)##

Rule Name: HUB Set Terminal and User - Clear
Event Name: Application Started
Rule Tags:
Custom Constraint List (0):
Execute Rule if: Matches

##Actions (2):##

HUB Store Setting Local

Constraint: (none)

settingName: currentTerminal
settingValue: {:CURRENTTERMINAL}
HUB Store Setting Local

Constraint: (none)

settingName: currentUser
settingValue:

##HUB Set Terminal and User - Login [User Logged In] (Rule)##

Rule Name: HUB Set Terminal and User - Login
Event Name: User Logged In
Rule Tags:
Custom Constraint List (0):
Execute Rule if: Matches

##Actions (2):##

HUB Store Setting Local

Constraint: (none)

settingName: currentTerminal
settingValue: {:CURRENTTERMINAL}
HUB Store Setting Local

Constraint: (none)

settingName: currentUser
settingValue: {:CURRENTUSER}

##HUB Set Terminal and User - Logout [User Logged Out] (Rule)##

Rule Name: HUB Set Terminal and User - Logout
Event Name: User Logged Out
Rule Tags:
Custom Constraint List (0):
Execute Rule if: Matches

##Actions (2):##

HUB Store Setting Local

Constraint: (none)

settingName: currentTerminal
settingValue: {:CURRENTTERMINAL}
HUB Store Setting Local

Constraint: (none)

settingName: currentUser
settingValue:

##HUB Message Received [Message Received] (Rule)##

Rule Name: HUB Message Received
Event Name: Message Received
Rule Tags:
Custom Constraint List (0):
Execute Rule if: Matches

##Actions (8):##

HUB Store Setting Local

Constraint: (none)

settingName: HUB_MESSAGE_RECEIVED
settingValue: [:Command]
HUB Store Setting Local

Constraint: (none)

settingName: HUB_EVENT_NAME
settingValue: {CALL:jsonData.jsonParse('[:Command]','eventName')}
HUB Store Setting Local

Constraint: (none)

settingName: HUB_EVENT_DATA
settingValue: {CALL:jsonData.jsonParse('[:Command]','eventData')}
HUB Store Setting Local

Constraint: (none)

settingName: HUB_EVENT_SID
settingValue: {CALL:jsonData.jsonParse('[:Command]','sid')}
HUB Store Setting Local

Constraint: (none)

settingName: HUB_EVENT_USERNAME
settingValue: {CALL:jsonData.jsonParse('[:Command]','userName')}
HUB Store Setting Local

Constraint: (none)

settingName: HUB_EVENT_TERMINAL
settingValue: {CALL:jsonData.jsonParse('[:Command]','terminal')}
HUB Store Setting Local

Constraint: (none)

settingName: HUB_EVENT_MESSAGE
settingValue: {CALL:jsonData.jsonParse('[:Command]','message')}
HUB ExecAMC

Constraint: (none)

AMCname: HUB Process Received Message
AMCvalue: [:Command]
runInBG:
delay:

##HUB Process Received Message [Automation Command Executed] (Rule)##

Rule Name: HUB Process Received Message
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (1):
Execute Rule if: Matches
Automation Command NameEqualsHUB Process Received Message

##Actions (4):##

HUB Store Setting Local

Constraint: (none)

settingName: CHAT_USR
settingValue: {CALL:chat.receive('[:CommandValue]','user')}
HUB Popup

Constraint: ‘{:HUB_EVENT_NAME}’ == ‘CHAT’ && ‘{:HUB_EVENT_USERNAME}’!=‘{:CURRENTUSER}’ && ‘{:HUB_EVENT_TERMINAL}’!=‘{:CURRENTTERMINAL}’

name: CHATMSG
title: CHAT Message
msg: {CALL:chat.receive('[:CommandValue]')}
color: Orange
AMCname: CHAT Show Chat Display
AMCvalue:
timeoutSecs: 5
HUB ExecAMC

Constraint: ‘{:HUB_EVENT_NAME}’ ==‘TASKS_COMPLETED_HTML’

AMCname: BB Refresh Widgets
AMCvalue:
runInBG:
delay:
HUB ExecAMC

Constraint: ‘{:HUB_EVENT_NAME}’==‘NAVIGATION’ && ‘{:HUB_EVENT_DATA}’==‘NAV_customer_display’

AMCname: HUB Broadcast - Send Current Ticket Data
AMCvalue:
runInBG:
delay: 1

##HUB Ticket Displayed [Ticket Displayed] (Rule)##

Rule Name: HUB Ticket Displayed
Event Name: Ticket Displayed
Rule Tags:
Custom Constraint List (0):
Execute Rule if: Matches

##Actions (1):##

HUB ExecAMC

Constraint: (none)

AMCname: HUB Broadcast - Send Current Ticket Data
AMCvalue:
runInBG:
delay:

##HUB Broadcast - Send Current Ticket Data [Automation Command Executed] (Rule)##

Rule Name: HUB Broadcast - Send Current Ticket Data
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (1):
Execute Rule if: Matches
Automation Command NameEqualsHUB Broadcast - Send Current Ticket Data

##Actions (3):##

HUB Store Setting Local

Constraint: (none)

settingName: TICKET_DATA
settingValue: {CALL:GQL.getCurrentTicket()}
HUB Store Setting Local

Constraint: (none)

settingName: HUB_CONTENT
settingValue: {"eventName":"TICKET_DISPLAYED","ticketData":[{:TICKET_DATA}], "paymentTypeName":"[:PaymentTypeName]", "tenderedAmount":"[:TenderedAmount]", "processedAmount":"[:ProcessedAmount]", "changeAmount":"[:ChangeAmount]", "paymentDescription":"[:Description]", "balance":"{BALANCE}"}
HUB Broadcast Message

Constraint: (none)

content: {:HUB_CONTENT}
refreshEntityScreens:

##HUB Broadcast - Close Ticket [Automation Command Executed] (Rule)##

Rule Name: HUB Broadcast - Close Ticket
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (2):
Execute Rule if: Matches
Automation Command NameEqualsHUB Close Ticket
Automation Command NameEqualsClose Ticket

##Actions (3):##

HUB Store Setting Local

Constraint: ‘[:AutomationCommandName]’ == ‘HUB Close Ticket’

settingName: HUB_CONTENT
settingValue: {"eventName":"CLOSE_TICKET_DELAYED", "ticketId":"[:CommandValue]"}
HUB Store Setting Local

Constraint: ‘[:AutomationCommandName]’ == ‘Close Ticket’

settingName: HUB_CONTENT
settingValue: {"eventName":"CLOSE_TICKET_NOW", "ticketId":"[:CommandValue]"}
HUB Broadcast Message

Constraint: (none)

content: {:HUB_CONTENT}
refreshEntityScreens:

##HUB Broadcast - Ticket Closed [Ticket Closed] (Rule)##

Rule Name: HUB Broadcast - Ticket Closed
Event Name: Ticket Closed
Rule Tags:
Custom Constraint List (0):
Execute Rule if: Matches

##Actions (2):##

HUB Store Setting Local

Constraint: (none)

settingName: HUB_CONTENT
settingValue: {"event":"TICKET_CLOSED", "ticketId":"[:TicketId]"}
HUB Broadcast Message

Constraint: (none)

content: {:HUB_CONTENT}
refreshEntityScreens:

##HUB Broadcast - Payment Processed [Payment Processed] (Rule)##

Rule Name: HUB Broadcast - Payment Processed
Event Name: Payment Processed
Rule Tags:
Custom Constraint List (0):
Execute Rule if: Matches

##Actions (4):##

HUB Store Setting Local

Constraint: (none)

settingName: TICKET_DATA
settingValue: {CALL:GQL.getCurrentTicket()}
HUB Store Setting Local

Constraint: (none)

settingName: HUB_CONTENT
settingValue: {"eventName":"PAYMENT_PROCESSED", "ticketData":[{:TICKET_DATA}], "paymentTypeName":"[:PaymentTypeName]", "tenderedAmount":"[:TenderedAmount]", "processedAmount":"[:ProcessedAmount]", "changeAmount":"[:ChangeAmount]", "paymentDescription":"[:Description]", "balance":"{BALANCE}"}
HUB Broadcast Message

Constraint: (none)

content: {:HUB_CONTENT}
refreshEntityScreens:
HUB ExecAMC

Constraint: [=TN(‘[:RemainingAmount]’)] <= 0

AMCname: HUB Close Ticket
AMCvalue: {TICKET ID}
runInBG:
delay:

##HUB Broadcast - Task Printed [Automation Command Executed] (Rule)##

Rule Name: HUB Broadcast - Task Printed
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (1):
Execute Rule if: Matches
Automation Command NameEqualsHUB Broadcast - Task Printed

##Actions (2):##

HUB Store Setting Local

Constraint: (none)

settingName: HUB_CONTENT
settingValue: {"eventName":"TASK_PRINTED","terminal":"{:CURRENTTERMINAL}","userName":"{:CURRENTUSER}","productType":"[:CommandValue]"}
HUB Broadcast Message

Constraint: (none)

content: {:HUB_CONTENT}
refreshEntityScreens:

##HUB Display Ticket in SambaPOS [Automation Command Executed] (Rule)##

Rule Name: HUB Display Ticket in SambaPOS
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (1):
Execute Rule if: Matches
Automation Command NameEqualsHUB Display Ticket in SambaPOS

##Actions (1):##

HUB Display Ticket

Constraint: (none)

ticketId: [:CommandValue]

#Customer Display

Use the HUB Actions and Rules. This Module also requires the Workperiod Status Report which is shown in the same section as the HUB Actions and Rules.

Additional components are shown next…

##Scripts

##GraphQL [GQL] (Script)##

Script Name: GraphQL
Script Handler: GQL

Script:

function getCurrentTicket(){
   var data = gql.Exec("{getCurrentTicket{id,number,date,type,uid,totalAmount,remainingAmount,entities{name,type},orders{quantity,name,portion,price,tags{tagName,tag,price,quantity,rate},states{stateName,state}}}}");
   return data;
}

#Actions

##BUS Store Setting [Update Program Setting] (Action)##

Action Name: BUS Store Setting
Action Type: Update Program Setting
###Parameters:###
Setting Name: [:settingName]
Setting Value: [:settingValue]
Update Type: Update
Is Local: [:isLocal]

##Rules

##BUS Store Business Settings [User Logged In] (Rule)##

Rule Name: BUS Store Business Settings
Event Name: User Logged In
Rule Tags:
Custom Constraint List (0):
Execute Rule if: Matches

##Actions (5):##

BUS Store Setting

Constraint: (none)

settingName: BUS_BusinessName
settingValue: My Awesome Restaurant
isLocal: False
BUS Store Setting

Constraint: (none)

settingName: BUS_VenueName
settingValue: My Awesome Restaurant
isLocal: False
BUS Store Setting

Constraint: (none)

settingName: BUS_MSG_Open
settingValue: Please place your Order when Ready!
isLocal: False
BUS Store Setting

Constraint: (none)

settingName: BUS_MSG_Closed
settingValue: Sorry, we are currently Closed.
isLocal: False
BUS Store Setting

Constraint: (none)

settingName: BUS_MSG_Welcome
settingValue: Welcome to
isLocal: False

##DEMO

Firefox on the left, SambaPOS on the right … there is a configurable timeout to go to “idle” screen after payment is made. This demo has it set to 15 seconds, so you will see a portion of the video where nothing is happening.

#Kitchen Display

Use the HUB Actions and Rules.

:exclamation: NOTE: The following documents the Actions/Rules that are required for the Javascript Kitchen Display Module. Actions required by a native Entity Screen are not shown, since the JS Module method supersedes the “old” method.

In addition, the Kitchen Display only supports a single display of a single productType … in this case, the setup for “Food” is shown, while “Drink” is not.

The Printer Template uses HTML and CSS to produce “colorful” Task Cards. You can edit zcss\sambapos.css to change the styles used in the Task Cards …

The Kitchen Display styles in sambapos.css are prefixed with KD_


##Task Type

Also, in the zconfigs\config.js file, find the following line and ensure it matches the Task Type Name that you used above …

var KD_HTMLtaskType = 'KD Task - Food';     // the KD Module works with this Task Type


##Printer

This is a Custom Printer for printing Tasks.

  • use a DOT (.) for the Printer Share Name / Port Name … if you don’t put a DOT (.) here, the Printer will not function!
  • click on the Settings link and enter the Task Type Name: KD Task - Food

##KD Printer - Food [Custom Printer] [Task Printer] (Printer)##

Printer Name: KD Printer - Food
Printer Share Name / Port Name: . (you must use a DOT here!)
Printer Type: Custom Printer [Task Printer]
Character Set: 437
RTL Support: None
Line Count: 0
Line Character Count: 42
Char Replacement:
Custom Task Printer Settings
Task Type: KD Task - Food

##Printer Template

##KD Template - Food (Printer Template)##

Template Name: KD Template - Food
Sort Orders:
Merge Lines: Don't Merge

Template:

[LAYOUT]
{ORDERS}

[ORDERS]
++{ORDER TIME} - {ENTITY NAME:Table} {ENTITY NAME:Customer}
(Id={NAME}-{ORDER UID})
(Color=#FF333333)
<div class="KD_Task_Product"><span class="KD_Task_Product_Quantity">[=('{QUANTITY}'>1 ? '{QUANTITY}' : '')]</span> {PRODUCT NAME}</div>
[='{PORTION}'=='' ? '' : '<div class="KD_Task_Portion">{PORTION}</div>']
{SORTED ORDER TAGS}

[ORDERS:Modified]
++{ORDER TIME} - {ENTITY NAME:Table} {ENTITY NAME:Customer}
(Id={NAME}-{ORDER UID})
(Color=#FF555533)
<div style="font-size:12px;">&nbsp;</div>
<div class="KD_Task_Modified">!!!! MODIFIED !!!!</div>
<div class="KD_Task_Product"><span class="KD_Task_Product_Quantity">[=('{QUANTITY}'>1 ? '{QUANTITY}' : '')]</span> {PRODUCT NAME}</div>
[='{PORTION}'=='' ? '' : '<div class="KD_Task_Portion">{PORTION}</div>']
{SORTED ORDER TAGS}

[ORDERS:Gift]
++{ORDER TIME} - {ENTITY NAME:Table} {ENTITY NAME:Customer}
(Id={NAME}-{ORDER UID})
(Color=#FF333333)
<div class="KD_Task_Product"><span class="KD_Task_Product_Quantity">[=('{QUANTITY}'>1 ? '{QUANTITY}' : '')]</span> {PRODUCT NAME}</div>
[='{PORTION}'=='' ? '' : '<div class="KD_Task_Portion">{PORTION}</div>']
{SORTED ORDER TAGS}

[ORDERS:Void]
++{ORDER TIME} - {ENTITY NAME:Table} {ENTITY NAME:Customer}
(Id={NAME}-{ORDER UID})
(Color=#FF550000)
<div style="font-size:12px;">&nbsp;</div>
<div class="KD_Task_Void">XXXX VOID XXXX</div>
<div class="KD_Task_Product">[=('{QUANTITY}'>1 ? ('{QUANTITY}'+'  ').substr(0,2) : '  ')] {PRODUCT NAME}</div>
[='{PORTION}'=='' ? '' : '<div class="KD_Task_Portion">{PORTION}</div>']

[SORTED ORDER TAGS:VIP Discount]
-- do not print

[SORTED ORDER TAGS:Happy Hour Discount]
-- do not print

[SORTED ORDER TAGS:Bread]
-- do not print

[SORTED ORDER TAGS:Meat]
<div class="KD_Task_OrderTags KD_Task_OrderTags_Meat"><span class="KD_Task_OrderTags_Quantity">[=('{ORDER TAG QUANTITY}'>1 ? '{ORDER TAG QUANTITY}'+'x' : '')]</span> {ORDER TAG NAME}</div>

[SORTED ORDER TAGS:Cheese]
<div class="KD_Task_OrderTags KD_Task_OrderTags_Cheese"><span class="KD_Task_OrderTags_Quantity">[=('{ORDER TAG QUANTITY}'>1 ? '{ORDER TAG QUANTITY}'+'x' : '')]</span> {ORDER TAG NAME}</div>

[SORTED ORDER TAGS:EXTRA Addons]
<div class="KD_Task_OrderTags KD_Task_OrderTags_ExtraAddons"><span class="KD_Task_OrderTags_Quantity">[=('{ORDER TAG QUANTITY}'>1 ? '{ORDER TAG QUANTITY}'+'x' : '')]</span> {ORDER TAG NAME}</div>

[SORTED ORDER TAGS:Veggies]
<div class="KD_Task_OrderTags KD_Task_OrderTags_Veggies"><span class="KD_Task_OrderTags_Quantity">[=('{ORDER TAG QUANTITY}'>1 ? '{ORDER TAG QUANTITY}'+'x' : '')]</span> {ORDER TAG NAME}</div>

[SORTED ORDER TAGS:Condiments]
<div class="KD_Task_OrderTags KD_Task_OrderTags_Condiments"><span class="KD_Task_OrderTags_Quantity">[=('{ORDER TAG QUANTITY}'>1 ? '{ORDER TAG QUANTITY}'+'x' : '')]</span> {ORDER TAG NAME}</div>

[SORTED ORDER TAGS:Sides]
<div class="KD_Task_OrderTags KD_Task_OrderTags_SideServings"><span class="KD_Task_OrderTags_Quantity">[=('{ORDER TAG QUANTITY}'>1 ? '{ORDER TAG QUANTITY}'+'x' : '')]</span> [='{ORDER TAG NAME}'.replace('Side ','')]</div>

[SORTED ORDER TAGS:Special Instructions]
<div class="KD_Task_OrderTags KD_Task_OrderTags_SpecialInstructions"><span class="KD_Task_OrderTags_Quantity">[=('{ORDER TAG QUANTITY}'>1 ? '{ORDER TAG QUANTITY}'+'x' : '')]</span> [='{ORDER TAG NAME}'.replace('SPIOTV ','')]</div>

[SORTED ORDER TAGS GROUP|ADD,Sauteed,OTS,Side,SPIOTV]

[SORTED ORDER TAGS GROUP:ADD]
<div class="KD_Task_OrderTagGroupHeader">     ~~~ ADDONS ~~~</div>

[SORTED ORDER TAGS GROUP:Sauteed]
<div class="KD_Task_OrderTagGroupHeader">     ~~~ SAUTEED ~~~</div>

[SORTED ORDER TAGS GROUP:OTS]
<div class="KD_Task_OrderTagGroupHeader">     ~~~ ON THE SIDE ~~~</div>

[SORTED ORDER TAGS GROUP:Side]
<div class="KD_Task_OrderTagGroupHeader">     ~~~ SIDES ~~~</div>

[SORTED ORDER TAGS GROUP:SPIOTV]
<div class="KD_Task_OrderTagGroupHeader">     ~~~ SPECIAL ~~~</div>


##Print Job

##KD Print Tasks - ANY [All Lines] (Print Job)##

Print Job Name: KD Print Tasks - ANY
Printing Content: All Lines
Always Exclude Tax: unchecked
Mappings (2)
DepartmentTicket TypeTerminalProduct GroupProduct TagProductPrinterPrinter Template
****productType=Food*KD Printer - FoodKD Template - Food
****productType=Drink*KD Printer - DrinkKD Template - Drink

##Custom Product Tag Caption

Create a Product Tag Caption in Manage > Settings > Program Settings with the name productType. This will be used to separate Food and Drink.


##Product Tag Editor

Set your Food and Drink Products to have the proper productType in Manage > Products > Product Tag Editor


##Entity Screen

##KD Kitchen Display [] (Entity Screen)##

Name: KD Kitchen Display
Ticket Type: Ticket
View Mode: Layout
Search Value Replace Pattern:
Appearance
Background Image:
Background Color: #00FFFFFF
Use State Display Format: unchecked
Column Count: 0
Row Count: 0
Button Height: 0
Page Count: 1
Font Size: 50
Header Button Font Size: 0
Entity List (0)
Entity Type:
Display State:
State Filter:
Entities: (none)
Details Template (none)(none)
Mappings (1)
Terminal User Role Department Ticket Type Visibility
****All

##Entity Screen Widgets

##KD HTML Viewer [Html Viewer] (Widget)##
Entity Screen: KD Kitchen Display

###Properties (13):###

Widget Type: Html Viewer
Name: KD HTML Viewer
X: 0
Y: 0
Height: 100
Width: 100
Zindex: 0
Angle: 0
Scale: 0
Corner Radius: 0
Auto Refresh: checked
Auto Refresh Interval: 0
Margin:

###Settings (4):###

Allow Scripting: checked
Is Toolbar Visible: unchecked
Url: http://localhost/?module=kitchen_display
Zoom: 100

##Automation Command

##KD Kitchen Display [KD Kitchen Display] (Automation Command)##

Name: KD Kitchen Display
Category: KD Kitchen Display
Button Header: Kitchen\rDisplay
Color: #FFCA6919
Font Size: 26
Confirmation: None
Values (0): (none)
Navigation Settings
Symbol:
Image:
Auto Refresh: 0
Tile Cache: 0
Navigation Module: Entity
Nav Module Parameter: KD Kitchen Display
Template: ```

</details>

<details>
<summary><b><u>Mappings</u></b></summary><table><tr><td><b>Terminal</b> </td><td><b>User Role</b> </td><td><b>Department</b> </td><td><b>Ticket Type</b> </td><td><b>Enabled States</b> </td><td><b>Visible States</b> </td><td><b>Visibility</b> </td></tr><tr><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td><td><code>Display on Navigation</code></td></tr></table></details>

----------

##Actions

<img src="/uploads/default/original/3X/7/4/748ed1ff1a088c0915542335b07bae22cc9909c7.png" width="690" height="427">

>##KD Update Order KDStatus `[Update Order State]` (Action)##
<table><tr><td><b>Action Name:</b> </td><td><code>KD Update Order KDStatus</code></td></tr><tr><td><b>Action Type:</b> </td><td><code>Update Order State</code></td></tr></table>
###Parameters:###
<table><tr><td><b>State Name:</b> </td><td><code>KDStatus</code></td></tr><tr><td><b>Group Order:</b> </td><td><code></code></td></tr><tr><td><b>Current State:</b> </td><td><code>[:CurrentState]</code></td></tr><tr><td><b>State:</b> </td><td><code>[:NewState]</code></td></tr><tr><td><b>State Order:</b> </td><td><code></code></td></tr><tr><td><b>State Value:</b> </td><td><code></code></td></tr></table>

----------

<img src="/uploads/default/original/3X/9/d/9da4633213c3cd7bebb5610ff1c06c2eb1b31e51.png" width="690" height="560">

>##KD Execute Print Job - ANY `[Execute Print Job]` (Action)##
<table><tr><td><b>Action Name:</b> </td><td><code>KD Execute Print Job - ANY</code></td></tr><tr><td><b>Action Type:</b> </td><td><code>Execute Print Job</code></td></tr></table>
###Parameters:###
<table><tr><td><b>Print Job Name:</b> </td><td><code>KD Print Tasks - ANY</code></td></tr><tr><td><b>Print Ticket:</b> </td><td><code>True</code></td></tr><tr><td><b>Ticket Ids:</b> </td><td><code></code></td></tr><tr><td><b>High Priority:</b> </td><td><code>[:hiPriority]</code></td></tr><tr><td><b>Order State Name:</b> </td><td><code>[:orderStateName]</code></td></tr><tr><td><b>Order State:</b> </td><td><code>[:orderState]</code></td></tr><tr><td><b>Order State Value:</b> </td><td><code></code></td></tr><tr><td><b>Order Tag Name:</b> </td><td><code></code></td></tr><tr><td><b>Order Tag Value:</b> </td><td><code></code></td></tr><tr><td><b>Ignore Selected Orders:</b> </td><td><code>[:ignoreSelectedOrders]</code></td></tr><tr><td><b>Copies:</b> </td><td><code>1</code></td></tr></table>

----------

##Rules

<img src="/uploads/default/original/3X/9/d/9d16d940859216040ee6b913b49d8ad74725b6be.png" width="690" height="725">

>##KD Order Added to Ticket - ANY `[Order Added]` (Rule)##
<table><tr><td><b>Rule Name:</b> </td><td><code>KD Order Added to Ticket - ANY</code></td></tr><tr><td><b>Event Name:</b> </td><td><code>Order Added</code></td></tr><tr><td><b>Rule Tags:</b> </td><td><code></code></td></tr></table>
<i>Custom Constraint List (0):</i>
<table><tr><td><b>Execute Rule if:</b> </td><td><code>Matches</code></td></tr></table>
<table></table>

##Actions (2):##

<details>
<summary><b><u>KD Update Order KDStatus</u></b></summary>

<b>Constraint:</b> <code>'{ITEM TAG:productType}' == 'Food'</code>
<table><tr><td><b>CurrentState:</b> </td><td><code> </code></td></tr><tr><td><b>NewState:</b> </td><td><code>FNotPrinted</code></td></tr></table></details>

<details>
<summary><b><u>KD Update Order KDStatus</u></b></summary>

<b>Constraint:</b> <code>'{ITEM TAG:productType}' == 'Drink'</code>
<table><tr><td><b>CurrentState:</b> </td><td><code> </code></td></tr><tr><td><b>NewState:</b> </td><td><code>DNotPrinted</code></td></tr></table></details>

##Mappings##



<details>
<summary><b><u>Mappings</u></b></summary><table><tr><td><b>Terminal</b> </td><td><b>User Role</b> </td><td><b>Department</b> </td><td><b>Ticket Type</b> </td></tr><tr><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td></tr></table></details>


----------

<img src="/uploads/default/original/3X/c/a/cafe4a82c8970752f88a38728ff487f1566ffbeb.png" width="690" height="716">

>##KD Update Printed Status - Food - Void or Cancel Void `[Automation Command Executed]` (Rule)##
<table><tr><td><b>Rule Name:</b> </td><td><code>KD Update Printed Status - Food - Void or Cancel Void</code></td></tr><tr><td><b>Event Name:</b> </td><td><code>Automation Command Executed</code></td></tr><tr><td><b>Rule Tags:</b> </td><td><code></code></td></tr></table>
<i>Custom Constraint List (3):</i>
<table><tr><td><b>Execute Rule if:</b> </td><td><code>Matches</code></td></tr></table>
<table><tr><td><code>Automation Command Name</code></td><td><b><i>Equals</i></b></td><td><code>Void</code></td></tr><tr><td><code>Automation Command Name</code></td><td><b><i>Equals</i></b></td><td><code>Cancel Void</code></td></tr><tr><td><code>{ITEM TAG:productType}</code></td><td><b><i>Contains</i></b></td><td><code>Food</code></td></tr></table>

##Actions (1):##

<details>
<summary><b><u>KD Update Order KDStatus</u></b></summary>

<b>Constraint:</b> <i>(none)</i>
<table><tr><td><b>CurrentState:</b> </td><td><code> </code></td></tr><tr><td><b>NewState:</b> </td><td><code>FNotPrinted</code></td></tr></table></details>

##Mappings##



<details>
<summary><b><u>Mappings</u></b></summary><table><tr><td><b>Terminal</b> </td><td><b>User Role</b> </td><td><b>Department</b> </td><td><b>Ticket Type</b> </td></tr><tr><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td></tr></table></details>


----------

<img src="/uploads/default/original/3X/9/9/991428b714e31d1a69b00042c5d4f91383e338cf.png" width="690" height="609">

>##KD Ticket Closing - Update KDStatus - Food `[Ticket Closing]` (Rule)##
<table><tr><td><b>Rule Name:</b> </td><td><code>KD Ticket Closing - Update KDStatus - Food</code></td></tr><tr><td><b>Event Name:</b> </td><td><code>Ticket Closing</code></td></tr><tr><td><b>Rule Tags:</b> </td><td><code></code></td></tr></table>
<i>Custom Constraint List (0):</i>
<table><tr><td><b>Execute Rule if:</b> </td><td><code>Matches</code></td></tr></table>
<table></table>

##Actions (1):##

<details>
<summary><b><u>KD Update Order KDStatus</u></b></summary>

<b>Constraint:</b> <i>(none)</i>
<table><tr><td><b>CurrentState:</b> </td><td><code>FNotPrinted</code></td></tr><tr><td><b>NewState:</b> </td><td><code>FPrinting</code></td></tr></table></details>

##Mappings##



<details>
<summary><b><u>Mappings</u></b></summary><table><tr><td><b>Terminal</b> </td><td><b>User Role</b> </td><td><b>Department</b> </td><td><b>Ticket Type</b> </td></tr><tr><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td></tr></table></details>


----------

<img src="/uploads/default/original/3X/4/1/4127485ec9b148571366b6e19a7b1144e1fbf96d.png" width="605" height="1000">

>##KD Print Order - Food `[Order State Updated]` (Rule)##
<table><tr><td><b>Rule Name:</b> </td><td><code>KD Print Order - Food</code></td></tr><tr><td><b>Event Name:</b> </td><td><code>Order State Updated</code></td></tr><tr><td><b>Rule Tags:</b> </td><td><code></code></td></tr></table>
<i>Custom Constraint List (4):</i>
<table><tr><td><b>Execute Rule if:</b> </td><td><code>Matches</code></td></tr></table>
<table><tr><td><code>State Name</code></td><td><b><i>Equals</i></b></td><td><code>KDStatus</code></td></tr><tr><td><code>State</code></td><td><b><i>Equals</i></b></td><td><code>FPrinting</code></td></tr><tr><td><code>Previous State</code></td><td><b><i>Equals</i></b></td><td><code>FNotPrinted</code></td></tr><tr><td><code>{ITEM TAG:productType}</code></td><td><b><i>Contains</i></b></td><td><code>Food</code></td></tr></table>

##Actions (3):##

<details>
<summary><b><u>KD Execute Print Job - ANY</u></b></summary>

<b>Constraint:</b> <i>(none)</i>
<table><tr><td><b>hiPriority:</b> </td><td><code> </code></td></tr><tr><td><b>orderStateName:</b> </td><td><code>KDStatus</code></td></tr><tr><td><b>orderState:</b> </td><td><code>FPrinting</code></td></tr><tr><td><b>ignoreSelectedOrders:</b> </td><td><code> </code></td></tr></table></details>

<details>
<summary><b><u>KD Update Order KDStatus</u></b></summary>

<b>Constraint:</b> <i>(none)</i>
<table><tr><td><b>CurrentState:</b> </td><td><code>FPrinting</code></td></tr><tr><td><b>NewState:</b> </td><td><code>FPrinted</code></td></tr></table></details>

<details>
<summary><b><u>HUB ExecAMC</u></b></summary>

<b>Constraint:</b> <i>(none)</i>
<table><tr><td><b>AMCname:</b> </td><td><code>HUB Broadcast - Task Printed</code></td></tr><tr><td><b>AMCvalue:</b> </td><td><code>{ITEM TAG:productType}</code></td></tr><tr><td><b>runInBG:</b> </td><td><code> </code></td></tr><tr><td><b>delay:</b> </td><td><code> </code></td></tr></table></details>

##Mappings##



<details>
<summary><b><u>Mappings</u></b></summary><table><tr><td><b>Terminal</b> </td><td><b>User Role</b> </td><td><b>Department</b> </td><td><b>Ticket Type</b> </td></tr><tr><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td><td><code>*</code></td></tr></table></details>

#CHAT

##Task Type

There used to be some Actions and Rules for CHAT, but I removed them. Instead, I embed the JS CHAT module in an HTML Viewer Widget on a Custom Entity Screen set to Layout Mode.


##Script

Used to parse the JSON string that contains the message, user, terminal, etc. This is used purely for when running the Module embedded in a SambaPOS HTML Viewer Widget, so that it can display a Popup in the bottom-right of the screen when a new message is received.

##CHAT [chat] (Script)##

Script Name: CHAT
Script Handler: chat

Script:

function receive(d,p) {
  var m = JSON.parse(d)
  var usr  = (m["userName"] ? m["userName"] : '');
  var term = (m["terminal"] ? m["terminal"] : '');
  var sid  = (m["sid"] ? m["sid"] : '');
  var msg  = (m["message"] ? m["message"] : '');

  switch (p) {
    case 'user':
      return usr;
      break;
    case 'terminal':
      return term;
      break;
    case 'sid':
      return sid;
      break;
    case 'msg':
      return msg;
      break;
    default:
      break;
  }

  return '['+term+'] '+usr+': '+msg;  
}

##Entity Screen

A Custom Entity Screen with the View Mode set to “Layout”.

:bulb: This technique can be used to embed ANY of the modules into SambaPOS. For example, I embedded Kitchen Display, Timeclock, and Reports in this way.

##CHAT Display [UNKNOWN] (Entity Screen)##

Name: CHAT Display
Ticket Type: Ticket
View Mode: Layout
Search Value Replace Pattern:
Appearance
Background Image:
Background Color: #00FFFFFF
Use State Display Format: unchecked
Column Count: 0
Row Count: 0
Button Height: 0
Page Count: 1
Font Size: 50
Header Button Font Size: 0
Entity List (0)
Entity Type: UNKNOWN
Display State:
State Filter:
Entities: (none)
Details Template (none)(none)
Mappings (1)
Terminal User Role Department Ticket Type Visibility
****All

##Automation Command

A button for your Nav Screen to load/display the Entity Screen.

##CHAT Show Chat Display [Messaging] (Automation Command)##

Name: CHAT Show Chat Display
Category: Messaging
Button Header: CHAT\rMessages
Color: #FFC0504D
Font Size: 26
Confirmation: None
Values (0): (none)
Navigation Settings
Symbol:
Image:
Auto Refresh: 0
Tile Cache: 0
Navigation Module: Entity
Nav Module Parameter: CHAT Display
Template: ``` [CHAT Messages:1] {REPORT TASK DETAILS: T.StartDate,'',T.StartTime,TSC.terminal,TSC.user,T.ContentText: (TST=CHAT Message Task) AND T.Completed=False: [=FD('{0}','yyyy-MMM-dd')] [=FD('{0}','ddd')] {2} {3} {4} >> {5} } ```
Mappings
Terminal User Role Department Ticket Type Enabled States Visible States Visibility
******Display on Navigation

##HTML Viewer Widget Settings

Navigate to the CHAT Screen, go into Design Mode, add an HTML Viewer Widget and modify the Settings as such:

  • URL: http://localhost/?module=chat

  • Enable Scripting: CHECKED

For the URL, supply the correct URL for your Webserver (and optionally the Port). The URL can be a hostname or IP address, and for most users, the Port can be ommitted. Then specify the Module that you wish to use in the HTML Viewer with this syntax:

http://yourServer:optionalPort/?module=chat

For other Modules, such as Kitchen Display, it would look like this:

http://localhost/?module=kitchen_display

:exclamation: The Allow Scripting CheckBox is required so that the JS can know that the Module is running within SambaPOS.

#Timeclock & Timeclock Policies

:warning: WARNING: The Reports (@@SQL handlers) for Timeclock use JSON functions available only in SQL 2016, so you will need to upgrade to SQL Express 2016 to use the Reporting features.


##Task Types

##TC Punch Task (Task Type)##

Name: TC Punch Task
Custom Fields(none)

##TC Punch Control Task (Task Type)##

Name: TC Punch Control Task
Custom Fields(none)

##TC Policy Task (Task Type)##

Name: TC Policy Task
Custom Fields
Field Name Field Type Editing Format Display Format
activeNumber
typeString
nameString
dateStartDate
dateEndDate
valueString

##Scripts (SQL)

##TC_EmployeeHours [@@TC_EmployeeHours] (Script)##

Script Name: TC_EmployeeHours
Script Handler: @@TC_EmployeeHours

Script:

-- Hours 15 and 30

-- PARM for Employee Entity Type
declare @entityType varchar(20)  = '@1'
-- PARM for Employee Name
declare @entityName varchar(20)  = '@2'
-- PARM for Date Filter Start
declare @StartDateIn varchar(25) = '@3'
-- PARM for Date Filter End
declare @EndDateIn varchar(25)   = '@4'

-- if Employee Entity Type is invalid, set default as 'Employees'
IF (@entityType = '') OR (@entityType is null) OR (@entityType = '$1') SET @entityType = 'Employees'

-- if Date Filter START is invalid, set default to beginning of Current Month
IF (@StartDateIn = '') OR (@StartDateIn is null) OR (@StartDateIn = '$3') SET @StartDateIn = left(CONVERT(VARCHAR(25), GETDATE(), 126),7)+'-01'

-- if Date Filter END is invalid, set a default
IF (@EndDateIn = '') OR (@EndDateIn is null) OR (@EndDateIn = '$4') SET @EndDateIn  = dateadd(Month,1,@StartDateIn)

-- set START and END date for Report Period
declare @StartDate datetime = convert(varchar(25),@StartDateIn,126)
declare @EndDate datetime = convert(varchar(25),@EndDateIn,126)
declare @EndDateInc datetime = convert(varchar(25),dateadd(day,-1,@EndDate),126)

-- set names of Employee Custom Data Rate Fields
declare @RateField1 varchar(20) = 'Rate1'
declare @RateField2 varchar(20) = 'Rate2'
declare @RateField3 varchar(20) = 'Rate3'




-- NOTHING TO SET BEYOND HERE

DECLARE @RATE1 money = 0
DECLARE @RATE2 money = 0
DECLARE @RATE3 money = 0

-- get RATES

SELECT
--[Name] as [EntityName]
  @RATE1 = SUM([R1])
, @RATE2 = SUM([R2])
, @RATE3 = SUM([R3])
FROM(
SELECT
 e.[Id]
,e.[Name]
--,e.[CustomData]
,cdName
,CASE cdName WHEN @RateField1 THEN convert(decimal(10,2),cdValue) ELSE 0.00 END as [R1]
,CASE cdName WHEN @RateField2 THEN convert(decimal(10,2),cdValue) ELSE 0.00 END as [R2]
,CASE cdName WHEN @RateField3 THEN convert(decimal(10,2),cdValue) ELSE 0.00 END as [R3]
FROM [Entities] e
JOIN [EntityTypes] et on et.[Id] = e.[EntityTypeId]
-- here we "join" the [Entities] table to itself and use the OPENJSON function
-- on the [CustomData] column
CROSS APPLY OPENJSON(e.[CustomData])
-- this WITH portion allows explicit definition of the schema JSON Keys for output
-- and gives references to the columns/field above in the SELECT portion
WITH (   
 cdName         varchar(50) '$.Name'
,cdValue        varchar(50) '$.Value'
) jsonData
WHERE et.[Name] = @entityType
   AND e.[Name] = @entityName
   AND cdName IN (@RateField1,@RateField2,@RateField3)
) rates
--GROUP BY [Name]



-- get Policies

DECLARE @policies table
(
[ID]  INT IDENTITY(1,1) NOT NULL 
,[taskId] int
,[taskName] varchar(255)
,[taskContent] varchar(255)
)

INSERT INTO @policies
SELECT
 t.[Id]
,t.[Name]
,t.[Content]
FROM [Tasks] t
JOIN [TaskTypes] tt on tt.[Id] = t.[TaskTypeId]
WHERE 1=1
AND tt.[Name] = 'TC Policy Task'
AND [Completed] = 0


-- Calculate

SELECT
-- [EmployeeName]
-- [StartDate]
--,[EndDate]
left(datename(YEAR,[StartDate]),4)+' '+left(datename(MONTH,[StartDate]),3)+' '+substring(convert(varchar,[StartDate],126),9,2) as [DT]
,left(DATENAME(weekday,[StartDate]),3) as [DT]
--,[H]
,[Hours]
,[REG]
,[OT]
,[HOL]
--,FORMAT(SUM([REG]*@RATE1 + [OT]*@RATE2 + [HOL]*@RATE3) ,'0000.00') as [EARNED]
FROM (
SELECT
 t.[Id]
,tt.[Name] as [TaskType]
,t.[Name] as [EmployeeName]
--,[Identifier]
--,[Completed]
--,[State]
--,substring(convert(varchar(10),[StartDate],126),6,5)
,[StartDate]
,[EndDate]
--,datediff(SECOND,t.[StartDate],t.[EndDate]) as [DUR_s]
--,datediff(SECOND,t.[StartDate],t.[EndDate])/60.0 as [DUR_m]
--,datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0 as [DUR_h]
,FORMAT(
datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0
,'0.00')
 as [Hours]

,CASE ISNULL(( SELECT [taskContent] FROM @policies WHERE [taskName] like '%Holiday%' AND [taskContent]=substring(convert(varchar(10),[StartDate],126),6,5) ),0) WHEN '0' THEN '0' ELSE '1' END as [H]

-- HOLIDAY
,FORMAT(
 CASE WHEN ISNULL(( SELECT [taskContent] FROM @policies WHERE [taskName] like '%Holiday%' AND [taskContent]=substring(convert(varchar(10),[StartDate],126),6,5) ),0) != '0'
 THEN datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0
 ELSE 0
 END ,'0.00') as [HOL]

-- OVERTIME
,FORMAT(
 CASE WHEN datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0  > ISNULL((SELECT [taskContent] FROM @policies WHERE [taskName] like '%OT Hour Limit%'),9999) AND (ISNULL(( SELECT [taskContent] FROM @policies WHERE [taskName] like '%Holiday%' AND [taskContent]=substring(convert(varchar(10),[StartDate],126),6,5) ),0) = '0')
 THEN (datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0) - ISNULL((SELECT [taskContent] FROM @policies WHERE [taskName] like '%OT Hour Limit%'),9999)
 ELSE 0
 END ,'0.00') as [OT]

-- REGULAR
,FORMAT(
 CASE WHEN (ISNULL(( SELECT [taskContent] FROM @policies WHERE [taskName] like '%Holiday%' AND [taskContent]=substring(convert(varchar(10),[StartDate],126),6,5) ),0) != '0')
 THEN 0
 ELSE CASE WHEN datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0  > ISNULL((SELECT [taskContent] FROM @policies WHERE [taskName] like '%OT Hour Limit%'),9999)
           THEN 8
	       ELSE (datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0)
	       END
 END ,'0.00') as [REG]

--,[LastUpdateTime]
--,[Content]
--,[CustomData]
--,[StateLog]
--,[UserName]
FROM [Tasks] t
JOIN [TaskTypes] tt on tt.[Id] = t.[TaskTypeId]
WHERE 1=1
AND t.[Completed] = 1
AND t.[Name] = @entityName
AND t.[StartDate] >= @StartDate
AND t.[StartDate] <  @EndDate
AND tt.[Name] = 'TC Punch Control Task'
--AND t.[State] = 'Punch Cycle Complete'
----ORDER BY t.[Name], t.[StartDate],t.[EndDate]
) HRS

GROUP BY
-- [EmployeeName]
 [StartDate]
--,[EndDate]
,left(datename(YEAR,[StartDate]),4)+' '+left(datename(MONTH,[StartDate]),3)+' '+substring(convert(varchar,[StartDate],126),9,2)
,left(DATENAME(weekday,[StartDate]),3)
--,[H]
,[Hours]
,[HOL]
,[OT]
,[REG]
ORDER BY 1,2,3


##TC_EmployeeHoursTTL [@@TC_EmployeeHoursTTL] (Script)##

Script Name: TC_EmployeeHoursTTL
Script Handler: @@TC_EmployeeHoursTTL

Script:

-- Hours 15 and 30

-- PARM for Employee Entity Type
declare @entityType varchar(20)  = '@1'
-- PARM for Employee Name
declare @entityName varchar(20)  = '@2'
-- PARM for Date Filter Start
declare @StartDateIn varchar(25) = '@3'
-- PARM for Date Filter End
declare @EndDateIn varchar(25)   = '@4'

-- if Employee Entity Type is invalid, set default as 'Employees'
IF (@entityType = '') OR (@entityType is null) OR (@entityType = '$1') SET @entityType = 'Employees'

-- if Date Filter START is invalid, set default to beginning of Current Month
IF (@StartDateIn = '') OR (@StartDateIn is null) OR (@StartDateIn = '$3') SET @StartDateIn = left(CONVERT(VARCHAR(25), GETDATE(), 126),7)+'-01'

-- if Date Filter END is invalid, set a default
IF (@EndDateIn = '') OR (@EndDateIn is null) OR (@EndDateIn = '$4') SET @EndDateIn  = dateadd(Month,1,@StartDateIn)

-- set START and END date for Report Period
declare @StartDate datetime = convert(varchar(25),@StartDateIn,126)
declare @EndDate datetime = convert(varchar(25),@EndDateIn,126)
declare @EndDateInc datetime = convert(varchar(25),dateadd(day,-1,@EndDate),126)

-- set names of Employee Custom Data Rate Fields
declare @RateField1 varchar(20) = 'Rate1'
declare @RateField2 varchar(20) = 'Rate2'
declare @RateField3 varchar(20) = 'Rate3'




-- NOTHING TO SET BEYOND HERE

DECLARE @RATE1 money = 0
DECLARE @RATE2 money = 0
DECLARE @RATE3 money = 0

-- get RATES

SELECT
--[Name] as [EntityName]
  @RATE1 = SUM([R1])
, @RATE2 = SUM([R2])
, @RATE3 = SUM([R3])
FROM(
SELECT
 e.[Id]
,e.[Name]
--,e.[CustomData]
,cdName
,CASE cdName WHEN @RateField1 THEN convert(decimal(10,2),cdValue) ELSE 0.00 END as [R1]
,CASE cdName WHEN @RateField2 THEN convert(decimal(10,2),cdValue) ELSE 0.00 END as [R2]
,CASE cdName WHEN @RateField3 THEN convert(decimal(10,2),cdValue) ELSE 0.00 END as [R3]
FROM [Entities] e
JOIN [EntityTypes] et on et.[Id] = e.[EntityTypeId]
-- here we "join" the [Entities] table to itself and use the OPENJSON function
-- on the [CustomData] column
CROSS APPLY OPENJSON(e.[CustomData])
-- this WITH portion allows explicit definition of the schema JSON Keys for output
-- and gives references to the columns/field above in the SELECT portion
WITH (   
 cdName         varchar(50) '$.Name'
,cdValue        varchar(50) '$.Value'
) jsonData
WHERE et.[Name] = @entityType
   AND e.[Name] = @entityName
   AND cdName IN (@RateField1,@RateField2,@RateField3)
) rates
--GROUP BY [Name]



-- get Policies

DECLARE @policies table
(
[ID]  INT IDENTITY(1,1) NOT NULL 
,[taskId] int
,[taskName] varchar(255)
,[taskContent] varchar(255)
)

INSERT INTO @policies
SELECT
 t.[Id]
,t.[Name]
,t.[Content]
FROM [Tasks] t
JOIN [TaskTypes] tt on tt.[Id] = t.[TaskTypeId]
WHERE 1=1
AND tt.[Name] = 'TC Policy Task'
AND [Completed] = 0


-- Calculate

SELECT
-- [EmployeeName]
-- [StartDate]
--,[EndDate]
 left(datename(YEAR,[StartDate]),4)+' '+left(datename(MONTH,[StartDate]),3)+' TTL' as [DT]
,'TOTALS'
--,left(DATENAME(weekday,[StartDate]),3) as [DT]
--,[H]
--,[Hours]
,FORMAT(sum([Hours]) ,'0.00') as [Hours]
,FORMAT(sum([REG]) ,'0.00') as [REG]
,FORMAT(sum([OT]) ,'0.00') as [OT]
,FORMAT(sum([HOL]) ,'0.00') as [HOL]
--,FORMAT(SUM([REG]*@RATE1 + [OT]*@RATE2 + [HOL]*@RATE3) ,'0000.00') as [EARNED]
FROM (
SELECT
 t.[Id]
,tt.[Name] as [TaskType]
,t.[Name] as [EmployeeName]
--,[Identifier]
--,[Completed]
--,[State]
--,substring(convert(varchar(10),[StartDate],126),6,5)
,[StartDate]
,[EndDate]
--,datediff(SECOND,t.[StartDate],t.[EndDate]) as [DUR_s]
--,datediff(SECOND,t.[StartDate],t.[EndDate])/60.0 as [DUR_m]
--,datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0 as [DUR_h]
,--FORMAT(
datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0
--,'000.00')
as [Hours]

,CASE ISNULL(( SELECT [taskContent] FROM @policies WHERE [taskName] like '%Holiday%' AND [taskContent]=substring(convert(varchar(10),[StartDate],126),6,5) ),0) WHEN '0' THEN '0' ELSE '1' END as [H]

-- HOLIDAY
,--FORMAT(
 CASE WHEN ISNULL(( SELECT [taskContent] FROM @policies WHERE [taskName] like '%Holiday%' AND [taskContent]=substring(convert(varchar(10),[StartDate],126),6,5) ),0) != '0'
 THEN datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0
 ELSE 0
 END-- ,'000.00')
  as [HOL]

-- OVERTIME
,--FORMAT(
 CASE WHEN datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0  > ISNULL((SELECT [taskContent] FROM @policies WHERE [taskName] like '%OT Hour Limit%'),9999) AND (ISNULL(( SELECT [taskContent] FROM @policies WHERE [taskName] like '%Holiday%' AND [taskContent]=substring(convert(varchar(10),[StartDate],126),6,5) ),0) = '0')
 THEN (datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0) - ISNULL((SELECT [taskContent] FROM @policies WHERE [taskName] like '%OT Hour Limit%'),9999)
 ELSE 0
 END-- ,'000.00')
  as [OT]

-- REGULAR
,--FORMAT(
 CASE WHEN (ISNULL(( SELECT [taskContent] FROM @policies WHERE [taskName] like '%Holiday%' AND [taskContent]=substring(convert(varchar(10),[StartDate],126),6,5) ),0) != '0')
 THEN 0
 ELSE CASE WHEN datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0  > ISNULL((SELECT [taskContent] FROM @policies WHERE [taskName] like '%OT Hour Limit%'),9999)
           THEN 8
	       ELSE (datediff(SECOND,t.[StartDate],t.[EndDate])/60.0/60.0)
	       END
 END-- ,'000.00')
  as [REG]

--,[LastUpdateTime]
--,[Content]
--,[CustomData]
--,[StateLog]
--,[UserName]
FROM [Tasks] t
JOIN [TaskTypes] tt on tt.[Id] = t.[TaskTypeId]
WHERE 1=1
AND t.[Completed] = 1
AND t.[Name] = @entityName
AND t.[StartDate] >= @StartDate
AND t.[StartDate] <  @EndDate
AND tt.[Name] = 'TC Punch Control Task'
--AND t.[State] = 'Punch Cycle Complete'
----ORDER BY t.[Name], t.[StartDate],t.[EndDate]
) HRS

GROUP BY
-- [EmployeeName]
-- [StartDate]
--,[EndDate]
left(datename(YEAR,[StartDate]),4)+' '+left(datename(MONTH,[StartDate]),3)+' TTL'
--,[H]
--,[Hours]
--,[HOL]
--,[OT]
--,[REG]
ORDER BY 1,2,3


##Reports

##TC Employee Hours [0] (Report)##

Report Name: TC Employee Hours
Page Size: 15cm
Display in Report Explorer: checked
Visual Printing: unchecked

Template:

[Current Hours $2:1,1, 1, 1, 1, 1]
>Date|Day|Hours|REG|OT|Start
{REPORT TASK DETAILS:
T.StartDate,T.StartTime
,=F([T.Duration]/60)
,=if([T.Duration]/60 > 8, 8, [T.Duration]/60)
,=if([T.Duration]/60 > 8, [T.Duration]/60 - 8, 0):
(TST=TC Punch Task) AND T.Completed=False AND T.State="Punched In" AND T.Name="$2":
[=FD('{0}','yyyy-MMM-dd')]|[=FD('{0}','ddd')]|[=F('{2}')]|[=F('{3}')]|[=F('{4}')]|{1}
}

[Previous Hours $2:2,1, 1, 1, 1, 1]
>Date|Day|Hours|REG|OT|HOL
@@TC_EmployeeHours:$1,$2,$3,$4,$5
>>Month| |Hours|REG|OT|HOL
>@@TC_EmployeeHoursTTL:$1,$2,$3,$4,$5

##Reports

Requires a special Report in SambaPOS:

###GQLM Custom Reports###

[Custom Reports:1,1,1,4,4,1,1,1,1]
>>id|reportType|displayInExplorer|name|template|pageSize|layouts|ortOrder|visualPrint
{REPORT SQL DETAILS:
SELECT
[Id]
,[ReportType]
,[DisplayInExplorer]
,[Name]
,convert(varchar(max),convert(varbinary(max),[Template]),2) as [Template]
,[PageSize]
,[Layouts]
,[SortOrder]
,[VisualPrint]
FROM [CustomReports]
WHERE [Name] not like 'GQLM%'
ORDER BY [SortOrder],[Name]
:F.Id,F.ReportType,F.DisplayInExplorer,F.Name,F.Template,F.PageSize,F.Layouts,F.SortOrder,F.VisualPrint}

##Ticket Explorer

Uses 1 of the HUB Rules/Actions (HUB Display Ticket in SambaPOS), but that is only required if the Module is embedded in SambaPOS HTML Viewer Widget, otherwise this Module has no special requirements.

##Task Editor

Requires 2 special Reports in SambaPOS:

###GQLM Task Types##

[Task Types:2,8]
>>id|name
{REPORT SQL DETAILS:
SELECT
 [Id]
,[Name]
FROM [TaskTypes]
ORDER BY [Name]
:F.Id,F.Name}

###GQLM Task Type Custom Fields###

[Task Type Custom Fields:1,1,4,4,2,2,2]
>>id|taskTypeId|taskType|name|fieldType|editingFormat|displayFormat
{REPORT SQL DETAILS:
SELECT
 cf.[Id]
,cf.[TaskTypeId]
,tt.[Name] as [TaskType]
,cf.[Name]
,CASE [FieldType]
 WHEN 0 THEN 'String'
 WHEN 1 THEN 'Number'
 WHEN 2 THEN 'Date'
 END as [FieldType]
,isnull([EditingFormat],'') as [EditingFormat]
,isnull([DisplayFormat],'') as [DisplayFormat]
FROM [TaskCustomFields] cf
JOIN [TaskTypes] tt on tt.[Id] = cf.[TaskTypeId]
ORDER BY tt.[Name], cf.[Id]
:F.Id,F.TaskTypeId,F.TaskType,F.Name,F.FieldType,F.EditingFormat,F.DisplayFormat}

1 Like

##Punch Editor

Not done.

##POS

Use the HUB Actions and Rules, and Special Reports.

:warning: Requires SambaPOS 5.1.61+

:bulb: TIP: Ensure you edit /zonfigs/config.js to set POS-specific parameters such as menuName, departmentName, ticketTypeName, etc.


###zconfigs\config.js###

POS configuration parameters:

// POS
var menuName        = 'Menu';
var departmentName  = 'Restaurant';
var ticketTypeName  = 'Ticket';
var POS_EntityTypes = ['Tables','Customers'];
var POS_EntityTypesAuto = false; // override static Entity Types above with automatic Ticket Type Entity Types
var POS_PrintJobs   = ['Print Bill','Print Orders to Kitchen Printer'];

3 Likes

You sir are a legend. The samba community is lucky to have you as a member!! @QMcKay

1 Like

OMG, gonna take a year to digest it :wink: )

1 Like

This is AWESOME! Can’t wait to play around with it! :smile:

@QMcKay maybe you can consider putting the code on GitHub, then if people want to start using (and modifying) this, it’s easy for them to get updates from your source and merge the changes?

1 Like

LOL, could do, but I don’t know how to use Github :stuck_out_tongue_winking_eye:

2 Likes

I am still reaching for the tissue box at the fact I need to understand such a beautiful thing!
Going to need an API for just hooking into the API…:sob:

2 Likes