Automatic discount based on customer discount-level with set amounts per product

I just updated to SQL Server 2014, imported @emre’s database into the instance, then changed the connection string to the new instance and database, and I can’t start SambaPOS anymore. It shows:

[General Info]

Application: SambaPOS
Version: 4.1.73
Region: en
DB: SQ
Machine: RUBEN-PC
User: ruben
Date: 11/3/2014
Time: 1:16 AM

User Explanation:

ruben said “”

[Exception Info 1]

Top-level Exception
Type: System.InvalidOperationException
Message: Cannot serialize a generic type 'System.Collections.Generic.List1[Samba.Domain.Models.Accounts.AccountTransaction]'. Source: PresentationFramework Stack Trace: at System.Windows.Markup.Primitives.MarkupWriter.VerifyTypeIsSerializable(Type type) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope) at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item) at System.Windows.Markup.Primitives.MarkupWriter.SaveAsXml(XmlWriter writer, MarkupObject item) at System.Windows.Markup.Primitives.MarkupWriter.SaveAsXml(XmlWriter writer, Object instance) at System.Windows.Markup.XamlWriter.Save(Object obj, TextWriter writer) at System.Windows.Markup.XamlWriter.Save(Object obj) at MS.Internal.Ink.ClipboardProcessor.CopySelectionInXAML(IDataObject dataObject, StrokeCollection strokes, List1 elements, Matrix transform, Size size)
at MS.Internal.Ink.ClipboardProcessor.CopySelectedData(IDataObject dataObject)
at System.Windows.Controls.InkCanvas.CopyToDataObject()
at System.Windows.Controls.InkCanvas.PrivateCopySelection()
at System.Windows.Controls.InkCanvas._OnCommandExecuted(Object sender, ExecutedRoutedEventArgs args)
at System.Windows.Input.CommandBinding.OnExecuted(Object sender, ExecutedRoutedEventArgs e)
at System.Windows.Input.CommandManager.ExecuteCommandBinding(Object sender, ExecutedRoutedEventArgs e, CommandBinding commandBinding)
at System.Windows.Input.CommandManager.FindCommandBinding(CommandBindingCollection commandBindings, Object sender, RoutedEventArgs e, ICommand command, Boolean execute)
at System.Windows.Input.CommandManager.FindCommandBinding(Object sender, RoutedEventArgs e, ICommand command, Boolean execute)
at System.Windows.Input.CommandManager.OnExecuted(Object sender, ExecutedRoutedEventArgs e)
at System.Windows.UIElement.OnExecutedThunk(Object sender, ExecutedRoutedEventArgs e)
at System.Windows.Input.ExecutedRoutedEventArgs.InvokeEventHandler(Delegate genericHandler, Object target)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
at System.Windows.Input.RoutedCommand.ExecuteImpl(Object parameter, IInputElement target, Boolean userInitiated)
at System.Windows.Input.RoutedCommand.ExecuteCore(Object parameter, IInputElement target, Boolean userInitiated)
at System.Windows.Input.CommandManager.TranslateInput(IInputElement targetElement, InputEventArgs inputEventArgs)
at System.Windows.UIElement.OnKeyDownThunk(Object sender, KeyEventArgs e)
at System.Windows.Input.KeyEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
at System.Windows.Interop.HwndKeyboardInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawKeyboardActions actions, Int32 scanCode, Boolean isExtendedKey, Boolean isSystemKey, Int32 virtualKey)
at System.Windows.Interop.HwndKeyboardInputProvider.ProcessKeyAction(MSG& msg, Boolean& handled)
at System.Windows.Interop.HwndSource.CriticalTranslateAccelerator(MSG& msg, ModifierKeys modifiers)
at System.Windows.Interop.HwndSource.OnPreprocessMessage(Object param)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority priority, Delegate method, Object arg)
at System.Windows.Interop.HwndSource.OnPreprocessMessageThunk(MSG& msg, Boolean& handled)
at System.Windows.Interop.HwndSource.WeakEventPreprocessMessage.OnPreprocessMessage(MSG& msg, Boolean& handled)
at System.Windows.Interop.ComponentDispatcherThread.RaiseThreadMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at Samba.Presentation.App.Main()


[Assembly Info]

Samba.Services, Version=1.0.0.0
mscorlib, Version=4.0.0.0
System, Version=4.0.0.0
Microsoft.Practices.Prism, Version=4.0.0.0
WindowsBase, Version=4.0.0.0
Samba.Infrastructure, Version=1.0.0.0
Samba.Infrastructure.Data, Version=1.0.0.0
PresentationFramework, Version=4.0.0.0
System.Xaml, Version=4.0.0.0
Samba.Presentation.Services, Version=1.0.0.0
FluentValidation, Version=3.4.0.0
Samba.Domain, Version=1.0.0.0
DevExpress.Xpf.Grid.v14.1, Version=14.1.6.0
DevExpress.Xpf.Grid.v14.1.Core, Version=14.1.6.0
PresentationCore, Version=4.0.0.0
Stateless, Version=1.0.0.0
System.Core, Version=4.0.0.0
System.Drawing, Version=4.0.0.0
System.Windows.Forms, Version=4.0.0.0
System.ComponentModel.Composition, Version=4.0.0.0
Samba.Localization, Version=1.0.0.0
Microsoft.CSharp, Version=4.0.0.0
Microsoft.Practices.ServiceLocation, Version=1.0.0.0
Samba.Persistance, Version=1.0.0.0
Microsoft.Practices.Prism.MefExtensions, Version=4.0.0.0
DevExpress.Xpf.Core.v14.1, Version=14.1.6.0
PropertyTools, Version=2012.4.14.1


[System Info]

Operating System
-Microsoft Windows 7 Ultimate
–CodeSet = 1252
–CSDVersion = Service Pack 1
–CurrentTimeZone = 480
–FreePhysicalMemory = 132248
–OSArchitecture = 32-bit
–OSLanguage = 1033
–ServicePackMajorVersion = 1
–ServicePackMinorVersion = 0
–Version = 6.1.7601

Machine
-RUBEN-PC
–Manufacturer = Gigabyte Technology Co., Ltd.
–Model = M52L-S3P
–TotalPhysicalMemory = 2145968128
–UserName = ruben-PC\ruben

That error means his file was made on a newer version than you have installed. I see your using 4.1.73 try installing 4.1.74 then start it. It looks like its off by just 1 version according to number sequence in the error.

Thanks, got it working now…

That works perfectly @emre! Thanks a lot, I’ll implement it in my setup at once…

I added a tutorial in the original post…

2 Likes

Thanks @pipo and everyone else involved in finding this solution, it has helped me greatly too. I found plenty of questions relating to product specific discounts but was struggling to implement the right solution myself. This question came just at the right time and was extremely helpful, thanks again.

2 Likes

On testing this it seems to incorrectly calculate this for portion sizes.

In the discount calculation [=-1*TN('{PRICE}')*(TN('{ITEM TAG:{ENTITY DATA:Customer:Discount}}')/100)] does {PRICE} take into account the portion price?

Thanks to @emre for putting this together, and thanks to you @pipo for posting the step-by-step.

This is a great implementation that a lot of people will use … so much so, it could almost become part of a default install :wink:

2 Likes

@CD1212 you can update discount when portion changes by handling Order Portion Changed event.

3 Likes

Thanks @emre thats great

I’ve uploaded 4.1.75 and it should work fine without Hard Refresh Ticket action.

2 Likes

It works perfectly as far as applying and calculating discounts via the Order Tags and Product Tags, but I have issue with the State display of both Ticket and Order in that they are not reverted to Inactive when they should be.

Either I set something wrong, or more checking/setting needs to be done to update the States.

Anybody else noticing this, or is it just me?

I believe this is a result of placing an Order (to a Table), closing Ticket, opening Ticket, selecting Customer Entity (States are changed accordingly), closing Ticket, opening Ticket, click Change Customer, remove Customer from Ticket (click the X). So we need a way to trap the removal of a Customer from the Ticket and update the States to InActive…

EDIT: This additional Rule seems to do the trick…

My Customer Entity uses 1,2,3 rather than D1, D2, D3. You may be able to make the Constraint as:

{ENTITY DATA:Customer:Discount} Is Null

Now it updates Order and Ticket States properly…

1 Like

@QMcKay I think I’ve used wrong term. Active / Inactive does not have any significance. When it appears as Active this means discount application procedure executed however there might be no discount available as there is no selected customer or there is no discount configured for product. Maybe clearing that state after Activate Discount is the best solution as active/inactive has no use other than debugging.

1 Like

Thanks for the explanation @emre… I understand now.

I like the way it is displaying with the additional Rule… I like information like this to be present so I know exactly what is going on in a Ticket or Order.

Though I suppose clearing the States when InActive would be just as good… hmm… I’ll think about that, and if I decide to go that way, I’ll post the appropriate Rule Changes.

2 Likes

If you have different portions, you’d better use a percentual discount (instead of a fixed price discount), since you cannot have the D1,D2&D3 tags set separately for different portions.
It should also be possible to change the system a bit so you can choose fixed or percentual discounts in the product tags. For example:

  • D1: 10%
    –> in the Update Discount when order discount becomes active rule you have the Update Order Discount action two times:
    – one with a constraint that checks if the last character is a “%”, with the Discount value calculating the discount as a percentage (the last character has to be stripped out first)
    – one with a constraint that checks if the last character is NOT a “%”, with the Discount value calculating the discount as a fixed discount

This way you can still use fixed discount amounts, but switch to percentual discounts for products with different portions.

I updated the first post to reflect the latest changes:

  • no need for hard refresh ticket action
  • portion change rule
  • Ticket entity changed and no customer selected --> deactivate ticket discount
1 Like

It would be nice if we could have these discounts included in the Discounts account.
I’m not sure how to best approach this though.

I’m thinking maybe this could work: at Payment Processed event, create an account transaction which puts the total discount amount in Discounts account, and also adds it to Sales account.

I’ve tried to access the total discount value with this (but it’s not working, I get 0.00 every time):

[=-1*({ORDER TAG TOTAL:D1}+{ORDER TAG TOTAL:D2}+{ORDER TAG TOTAL:D3})]

It seems like this information is not available at Payment processed event…

EDIT: I forgot to use TN in the expression, I know it should be like this:

[=-1*(TN(‘{ORDER TAG TOTAL:D1}’)+TN(‘{ORDER TAG TOTAL:D2}’)+TN('{ORDER TAG TOTAL:D3}))]

but even just {ORDER TAG TOTAL:D1} or {ORDER TAG TOTAL:D2} or {ORDER TAG TOTAL:D3} show 0.00 in a Show Message action…
In the Order State Updated and Ticket Total Changed events it’s also not available…

EDIT: @Jesse just pointed out to me that D1 is the tag’s value, not it’s name (duh) :smile:
so we just need {ORDER TAG TOTAL:Discount}

1 Like

I’m getting closer to a solution without need for ORDER TAG TOTAL, but I ran into an issue with the Order Untagged event.
It has a variable called [:OrderTagPrice] which always has null as value when it’s triggered after cancelling/voiding an order.
Is this normal behavior @emre? Is there any way we can get the order tag’s price at Order Untagged event?

It also has variables [:OldOrderTagCount] and [:OldOrderTagQuantity], but no variable like [:OldOrderTagPrice].

What I’m doing is this:
When an order is cancelled or voided, I call the Untag Order Action to remove the discount tag.
At Order Untagged event, I want to deduct the order tag’s price from the ticket total discount (a program setting).
But at this time the order tag’s price is not available for some reason. I don’t know if this is something that can be resolved or not…

EDIT: Nevermind, I’m going for the first way to do it with ORDER TAG TOTAL (no need for all this)

Hmm… not sure what I did differently this time, but since I set this up a 2nd time from scratch just now, I find that I need to remove {QUANTITY} from the calculation. My setup is slightly different, as you see here:

[=-1*(TN('{ITEM TAG:D{ENTITY DATA:Customer:VIP Level}}'))]

Also, I’ve added more constraints to 2 current Rules and 2 new Rules to handle Gifting/UnGifting.

Now I need to take a look at how to prevent applying a Discount if we are in Happy Hour (Price Tag HH)!

EDIT: Adding this constraint does the trick.

{PRICE TAG} Not Equals HH

@emre, how do I read the value of the Current Price Definition in use?

Even though the Rule Constraints shown in the above Rules use {PRICE TAG} and that works properly, I cannot for the life of me figure out how to display the Price Tag. Specifically, I was hoping to display it in Application Subtitle, and Popup. I have tried:

{PRICE TAG}
{:PRICETAG}
[:PriceTag]
{:PRICE TAG}
{SETTING:PRICE TAG}
{:CURRENTPRICETAG}

You can store it as a program setting at the moment you switch active price tag.

1 Like