Query on PXFormula - erp

Just want to ask what could be the problem in my coding below, I want to automatically update the Tax Rate column whenever the user selects an ATC which is Tax ID technically. I successfully created a selector for ATC but it does not update the Tax Rate after selection. Below is my DAC, thanks in advance.
#region Atc
public abstract class atc : PX.Data.IBqlField
{
}
protected String _Atc;
[PXDBString(15, IsUnicode = true)]
[PXUIField(DisplayName = "ATC")]
//[PXSelector(typeof(Search<Tax.taxID, Where<Tax.taxType, Equal<CSTaxType.withholding>>>),
// new Type[] {typeof(Tax.descr)})]
[PXSelector(typeof(Search<Tax.taxID, Where<Tax.taxType, Equal<CSTaxType.withholding>>>))]
public virtual String Atc
{
get
{
return this._Atc;
}
set
{
this._Atc = value;
}
}
#endregion
#region Atcrate
public abstract class atcrate : PX.Data.IBqlField
{
}
protected Decimal? _Atcrate;
[PXDBDecimal()]
[PXFormula(typeof(Selector<IISI_ARAdjust_Ext.atc, TaxRev.taxRate>))]
[PXUIField(DisplayName = "ATC Rate")]
public virtual Decimal? Atcrate
{
get
{
return this._Atcrate;
}
set
{
this._Atcrate = value;
}
}
#endregion

Add CommitChanges="True" to the Selector for "Atc" in the Selector like below
<px:PXSelector CommitChanges="True" ID="edAtc" runat="server" AutoRefresh="True" DataField="ATC"/>
then update the Tax Rate in A Field Updated event like below:
public virtual void YourDAC_Atc_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
YourDAC var = (YourDAC)e.Row;
if (var == null)
{
return;
}
TaxRate = Formula to get the tax Rate based on the current Atc }

Related

Is running a query from a command a violation of Command-Query Separation?

Given a real-world anonymous shopping cart, the "AddToCart" workflow must do the following steps:
Lookup the current product from the database. Get the price from the product or use a service to calculate the price on user selections and other product properties. (query)
Lookup the current shopping cart from the database. (query)
If the current shopping cart doesn't exist in the database, create a new shopping cart entity (in memory).
Add the new item (product) to the shopping cart entity (in memory) along with its price.
Run any discount calculations on the entire shopping cart. (depends on query)
Run any sales tax calculations on the shopping cart. (depends on query)
Run any shipping calculations on the shopping cart. (depends on query)
If this is a new shopping cart, add the entity to the database, otherwise update the shopping cart in the database. (command)
So, although "AddToCart" sounds like it should be a command (since it updates the system state), in practice it depends on many queries.
My Question
What is the generally accepted way to handle workflows like this?
Make an AddToCartCommandHandler that depends on other services that may run queries.
Make a facade CartService that orchestrates the workflow that runs the queries first followed by the commands.
Make the controller action method first run the queries, then run any commands. Seems like some of the query steps could be missed if this needs to be reused.
Other?
Is the reason I can't find an answer about this because it "depends on the design" and this is one of the exceptions where not to apply it?
If the commands and queries are separated, would I pass my real entity framework entity class to the command that adds/updates the cart (so EF can work out whether it is attached or not)? It seems like a DTO won't do in this case.
NOTE: I am implicitly assuming that systems that implement CQS do so with the aim that eventually they could become a full-on CQRS system. If so, this workflow apparently would not be able to make the transition - hence my question.
Background
I am taking my first stab at CQS.
It is clear from the documentation I have read about this pattern that a query must not change the system state.
However, it is unclear whether it is considered okay to run a query from within a command (I can't seem to find any info anywhere).
There are several real-world cases I can think of where this needs to happen. But, given the lack of real-world examples of this pattern online I am uncertain how to proceed. There is lots of theory online, but the only code I can find is here and here.
The answer to this problem came in the form of a comment by qujck.
The solution is to break the application into different query types and command types. The exact purpose of each type remain a mystery (since the blog post doesn't go into the reasons why he made this distinction), but it does make it clear how top-level and mid-level commands can depend on database queries.
Command Types
Command (top-level)
Command Strategy (mid-level)
Data Command (direct data access)
Query Types
Query (top-level)
Query Strategy (mid-level)
Data Query (direct data access)
Command-Query Implementation
// Commands
public interface ICommand
{
}
public interface IDataCommand
{
}
/// <summary>
/// A holistic abstraction, an abstraction that acts as the whole of each transaction
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
public interface ICommandStrategyHandler<TCommand> where TCommand : ICommand
{
void Handle(TCommand command);
}
/// <summary>
/// Direct database update
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public interface IDataCommandHandler<TCommand> where TCommand : IDataCommand
{
void Handle(TCommand command);
}
// Queries
public interface IQuery<TResult>
{
}
public interface IDataQuery<TResult>
{
}
/// <summary>
/// A holistic abstraction, an abstraction that acts as the whole of each transaction
/// </summary>
/// <typeparam name="TQuery"></typeparam>
/// <typeparam name="TResult"></typeparam>
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
public interface IQueryStrategyHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
/// <summary>
/// Direct database query
/// </summary>
/// <typeparam name="TQuery"></typeparam>
/// <typeparam name="TResult"></typeparam>
public interface IDataQueryHandler<TQuery, TResult> where TQuery : IDataQuery<TResult>
{
TResult Handle(TQuery query);
}
/// <summary>
/// Generic processor that can run any query
/// </summary>
public interface IQueryProcessor
{
TResult Execute<TResult>(IQuery<TResult> query);
// NOTE: Stephen recommends against using Async. He may be right that it is not
// worth the aggrevation of bugs that may be introduced.
//Task<TResult> Execute<TResult>(IQuery<TResult> query);
TResult Execute<TResult>(IDataQuery<TResult> query);
}
AddToCart Dependency Graph
Using the above implementation, the structure of the AddToCart workflow dependency graph looks like this.
AddToCartCommandHandler : ICommandHandler<AddToCartCommand>
GetShoppingCartDetailsQueryHandler : IQueryHandler<GetShoppingCartDetailsQuery, ShoppingCartDetails>
GetShoppingCartQueryStrategyHandler : IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails>
GetShoppingCartDataQueryHandler : IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails>
ApplicationDbContext
CreateShoppingCartDataCommandHandler : IDataCommandHandler<CreateShoppingCartDataCommand>
ApplicationDbContext
UpdateShoppingCartDataCommandHandler : IDataCommandHandler<UpdateShoppingCartDataCommand>
SetItemPriceCommandStrategyHandler : ICommandStrategyHandler<SetItemPriceCommandStrategy>
GetProductDetailsDataQueryHandler : IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails>
ApplicationDbContext
SetTotalsCommandStrategyHandler : ICommandStrategyHandler<SetTotalsCommandStrategy>
SetDiscountsCommandStrategyHandler : ICommandStrategyHandler<SetDiscountsCommandStrategy>
?
SetSalesTaxCommandStrategyHandler : ICommandStrategyHandler<SetSalesTaxCommandStrategy>
Implementation
DTOs
public class ShoppingCartDetails : IOrder
{
private IEnumerable<IOrderItem> items = new List<ShoppingCartItem>();
public Guid Id { get; set; }
public decimal SubtotalDiscounts { get; set; }
public string ShippingPostalCode { get; set; }
public decimal Shipping { get; set; }
public decimal ShippingDiscounts { get; set; }
public decimal SalesTax { get; set; }
public decimal SalesTaxDiscounts { get; set; }
// Declared twice - once for the IOrder interface
// and once so we can get the realized concrete type.
// See: https://stackoverflow.com/questions/15490633/why-cant-i-use-a-compatible-concrete-type-when-implementing-an-interface
public IEnumerable<ShoppingCartItem> Items
{
get { return this.items as IEnumerable<ShoppingCartItem>; }
set { this.items = value; }
}
IEnumerable<IOrderItem> IOrder.Items
{
get { return this.items; }
set { this.items = value; }
}
//public IEnumerable<ShoppingCartNotification> Notifications { get; set; }
//public IEnumerable<ShoppingCartCoupon> Coupons { get; set; } // TODO: Add this to IOrder
}
public class ShoppingCartItem : IOrderItem
{
public ShoppingCartItem()
{
this.Id = Guid.NewGuid();
this.Selections = new Dictionary<string, object>();
}
public Guid Id { get; set; }
public Guid ShoppingCartId { get; set; }
public Guid ProductId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public decimal PriceDiscount { get; set; }
public IDictionary<string, object> Selections { get; set; }
}
public class ProductDetails
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public decimal Discount { get; set; }
}
Calculating Order Totals
Rather than relying on a string of services to do simple (and required) arithmetic, I opted to put this behavior into extension methods so it is done on the fly against the actual data. Since this logic will need to be shared between the shopping cart, order, and quote, the calculation is done against IOrder and IOrderItem rather than concrete model types.
// Contract to share simple cacluation and other business logic between shopping cart, order, and quote
public interface IOrder
{
decimal SubtotalDiscounts { get; set; }
decimal Shipping { get; set; }
decimal ShippingDiscounts { get; set; }
decimal SalesTax { get; set; }
decimal SalesTaxDiscounts { get; set; }
IEnumerable<IOrderItem> Items { get; set; }
}
public interface IOrderItem
{
Guid ProductId { get; set; }
int Quantity { get; set; }
decimal Price { get; set; }
decimal PriceDiscount { get; set; }
IDictionary<string, object> Selections { get; set; }
}
public static class OrderExtensions
{
public static decimal GetSubtotal(this IOrder order)
{
return order.Items.Sum(x => x.GetTotal());
}
public static decimal GetSubtotalBeforeDiscounts(this IOrder order)
{
return order.Items.Sum(x => x.GetTotalBeforeDiscounts());
}
public static decimal GetTotal(this IOrder order)
{
var subtotal = (order.GetSubtotal() - order.SubtotalDiscounts);
var shipping = (order.Shipping - order.ShippingDiscounts);
var salesTax = (order.SalesTax - order.SalesTaxDiscounts);
return (subtotal + shipping + salesTax);
}
}
public static class OrderItemExtensions
{
public static decimal GetTotalBeforeDiscounts(this IOrderItem item)
{
return (item.Price * item.Quantity);
}
public static decimal GetTotal(this IOrderItem item)
{
return (GetTotalBeforeDiscounts(item) - item.PriceDiscount);
}
public static decimal GetDiscountedUnitPrice(this IOrderItem item)
{
return (item.Quantity > 0) ? (GetTotal(item) / item.Quantity) : 0;
}
}
ShoppingCartController
For brevity, we only show the AddToCart action, but this is where other actions against the shopping cart (i.e. remove from cart) would go as well.
public class ShoppingCartController : Controller
{
private readonly IQueryProcessor queryProcessor;
private readonly IAnonymousIdAccessor anonymousIdAccessor;
private readonly ICommandHandler<AddToCartCommand> addToCartHandler;
public ShoppingCartController(
IQueryProcessor queryProcessor,
IAnonymousIdAccessor anonymousIdAccessor,
ICommandHandler<AddToCartCommand> addToCartHandler)
{
if (queryProcessor == null)
throw new ArgumentNullException("queryProcessor");
if (anonymousIdAccessor == null)
throw new ArgumentNullException("anonymousIdAccessor");
if (addToCartHandler == null)
throw new ArgumentNullException("addToCartHandler");
this.queryProcessor = queryProcessor;
this.anonymousIdAccessor = anonymousIdAccessor;
this.addToCartHandler = addToCartHandler;
}
public ActionResult Index()
{
var command = new GetShoppingCartDetailsQuery
{
ShoppingCartId = this.anonymousIdAccessor.AnonymousID
};
ShoppingCartDetails cart = this.queryProcessor.Execute(command);
return View(cart);
}
public ActionResult AddToCart(ItemViewModel model)
{
var command = new AddToCartCommand
{
ProductId = model.Id,
Quantity = model.Qty,
Selections = model.Selections,
ShoppingCartId = this.anonymousIdAccessor.AnonymousID
};
this.addToCartHandler.Handle(command);
// If we execute server side, it should go to the cart page
return RedirectToAction("Index");
}
}
AddToCartCommandHandler
Here is where the main part of the workflow is executed. This command will be called directly from the AddToCart controller action.
public class AddToCartCommandHandler : ICommandHandler<AddToCartCommand>
{
private readonly IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails> getShoppingCartQuery;
private readonly IDataCommandHandler<UpdateShoppingCartDataCommand> updateShoppingCartCommand;
private readonly ICommandStrategyHandler<SetItemPriceCommandStrategy> setItemPriceCommand;
private readonly ICommandStrategyHandler<SetTotalsCommandStrategy> setTotalsCommand;
public AddToCartCommandHandler(
IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails> getShoppingCartCommand,
IDataCommandHandler<UpdateShoppingCartDataCommand> updateShoppingCartCommand,
ICommandStrategyHandler<SetItemPriceCommandStrategy> setItemPriceCommand,
ICommandStrategyHandler<SetTotalsCommandStrategy> setTotalsCommand
)
{
if (getShoppingCartCommand == null)
throw new ArgumentNullException("getShoppingCartCommand");
if (setItemPriceCommand == null)
throw new ArgumentNullException("setItemPriceCommand");
if (updateShoppingCartCommand == null)
throw new ArgumentNullException("updateShoppingCartCommand");
if (setTotalsCommand == null)
throw new ArgumentNullException("setTotalsCommand");
this.getShoppingCartQuery = getShoppingCartCommand;
this.updateShoppingCartCommand = updateShoppingCartCommand;
this.setItemPriceCommand = setItemPriceCommand;
this.setTotalsCommand = setTotalsCommand;
}
public void Handle(AddToCartCommand command)
{
// Get the shopping cart (aggregate root) from the database
var shoppingCart = getShoppingCartQuery.Handle(new GetShoppingCartQueryStrategy { ShoppingCartId = command.ShoppingCartId });
// Create a new shopping cart item
var item = new Contract.DTOs.ShoppingCartItem
{
ShoppingCartId = command.ShoppingCartId,
ProductId = command.ProductId,
Quantity = command.Quantity,
// Dictionary representing the option selections the user made on the UI
Selections = command.Selections
};
// Set the item's price (calculated/retrieved from database query)
setItemPriceCommand.Handle(new SetItemPriceCommandStrategy { ShoppingCartItem = item });
// Add the item to the cart
var items = new List<Contract.DTOs.ShoppingCartItem>(shoppingCart.Items);
items.Add(item);
shoppingCart.Items = items;
// Set the shopping cart totals (sales tax, discounts)
setTotalsCommand.Handle(new SetTotalsCommandStrategy { ShoppingCart = shoppingCart });
// Update the shopping cart details in the database
updateShoppingCartCommand.Handle(new UpdateShoppingCartDataCommand { ShoppingCart = shoppingCart });
}
}
GetShoppingCartQueryStrategyHandler
public class GetShoppingCartQueryStrategyHandler : IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails>
{
private readonly IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails> getShoppingCartDataQuery;
private readonly IDataCommandHandler<CreateShoppingCartDataCommand> createShoppingCartDataCommand;
public GetShoppingCartQueryStrategyHandler(
IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails> getShoppingCartDataQuery,
IDataCommandHandler<CreateShoppingCartDataCommand> createShoppingCartDataCommand)
{
if (getShoppingCartDataQuery == null)
throw new ArgumentNullException("getShoppingCartDataQuery");
if (createShoppingCartDataCommand == null)
throw new ArgumentNullException("createShoppingCartDataCommand");
this.getShoppingCartDataQuery = getShoppingCartDataQuery;
this.createShoppingCartDataCommand = createShoppingCartDataCommand;
}
public ShoppingCartDetails Handle(GetShoppingCartQueryStrategy query)
{
var result = this.getShoppingCartDataQuery.Handle(new GetShoppingCartDataQuery { ShoppingCartId = query.ShoppingCartId });
// If there is no shopping cart, create one.
if (result == null)
{
this.createShoppingCartDataCommand.Handle(new CreateShoppingCartDataCommand { ShoppingCartId = query.ShoppingCartId });
result = new ShoppingCartDetails
{
Id = query.ShoppingCartId
};
}
return result;
}
}
GetShoppingCartDataQueryHandler
/// <summary>
/// Data handler to get the shopping cart data (if it exists)
/// </summary>
public class GetShoppingCartDataQueryHandler : IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails>
{
private readonly IAppContext context;
public GetShoppingCartDataQueryHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public ShoppingCartDetails Handle(GetShoppingCartDataQuery query)
{
return (from shoppingCart in context.ShoppingCarts
where shoppingCart.Id == query.ShoppingCartId
select new ShoppingCartDetails
{
Id = shoppingCart.Id,
SubtotalDiscounts = shoppingCart.SubtotalDiscounts,
ShippingPostalCode = shoppingCart.ShippingPostalCode,
Shipping = shoppingCart.Shipping,
ShippingDiscounts = shoppingCart.ShippingDiscounts,
SalesTax = shoppingCart.SalesTax,
SalesTaxDiscounts = shoppingCart.SalesTaxDiscounts,
Items = shoppingCart.Items.Select(i =>
new Contract.DTOs.ShoppingCartItem
{
Id = i.Id,
ShoppingCartId = i.ShoppingCartId,
ProductId = i.ProductId,
Quantity = i.Quantity,
Price = i.Price,
PriceDiscount = i.PriceDiscount
// TODO: Selections...
})
}).FirstOrDefault();
}
}
CreateShoppingCartDataCommandHandler
public class CreateShoppingCartDataCommandHandler : IDataCommandHandler<CreateShoppingCartDataCommand>
{
private readonly IAppContext context;
public CreateShoppingCartDataCommandHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public void Handle(CreateShoppingCartDataCommand command)
{
var cart = new ShoppingCart
{
Id = command.ShoppingCartId
};
this.context.ShoppingCarts.Add(cart);
this.context.SaveChanges();
}
}
UpdateShoppingCartDataCommandHandler
This updates the shopping cart with all of the changes that the business layer applied.
For the time being, this "command" does a query so it can reconcile the differences between the database and in memory copy. However, it is obviously a violation of the CQS pattern. I plan to make a follow-up question to determine what the best course of action is for change tracking since change tracking and CQS appear to be intimately linked.
public class UpdateShoppingCartDataCommandHandler : IDataCommandHandler<UpdateShoppingCartDataCommand>
{
private readonly IAppContext context;
public UpdateShoppingCartDataCommandHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public void Handle(UpdateShoppingCartDataCommand command)
{
var cart = context.ShoppingCarts
.Include(x => x.Items)
.Single(x => x.Id == command.ShoppingCart.Id);
cart.Id = command.ShoppingCart.Id;
cart.SubtotalDiscounts = command.ShoppingCart.SubtotalDiscounts;
cart.ShippingPostalCode = command.ShoppingCart.ShippingPostalCode;
cart.Shipping = command.ShoppingCart.Shipping;
cart.ShippingDiscounts = command.ShoppingCart.ShippingDiscounts;
cart.SalesTax = command.ShoppingCart.SalesTax;
cart.SalesTaxDiscounts = command.ShoppingCart.SalesTaxDiscounts;
ReconcileShoppingCartItems(cart.Items, command.ShoppingCart.Items, command.ShoppingCart.Id);
// Update the cart with new data
context.SaveChanges();
}
private void ReconcileShoppingCartItems(ICollection<ShoppingCartItem> items, IEnumerable<Contract.DTOs.ShoppingCartItem> itemDtos, Guid shoppingCartId)
{
// remove deleted items
var items2 = new List<ShoppingCartItem>(items);
foreach (var item in items2)
{
if (!itemDtos.Any(x => x.Id == item.Id))
{
context.Entry(item).State = EntityState.Deleted;
}
}
// Add/update items
foreach (var dto in itemDtos)
{
var item = items.FirstOrDefault(x => x.Id == dto.Id);
if (item == null)
{
items.Add(new ShoppingCartItem
{
Id = Guid.NewGuid(),
ShoppingCartId = shoppingCartId,
ProductId = dto.ProductId,
Quantity = dto.Quantity,
Price = dto.Price,
PriceDiscount = dto.PriceDiscount
});
}
else
{
item.ProductId = dto.ProductId;
item.Quantity = dto.Quantity;
item.Price = dto.Price;
item.PriceDiscount = dto.PriceDiscount;
}
}
}
}
SetItemPriceCommandStrategyHandler
public class SetItemPriceCommandStrategyHandler : ICommandStrategyHandler<SetItemPriceCommandStrategy>
{
private readonly IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails> getProductDetailsQuery;
public SetItemPriceCommandStrategyHandler(
IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails> getProductDetailsQuery)
{
if (getProductDetailsQuery == null)
throw new ArgumentNullException("getProductDetailsQuery");
this.getProductDetailsQuery = getProductDetailsQuery;
}
public void Handle(SetItemPriceCommandStrategy command)
{
var shoppingCartItem = command.ShoppingCartItem;
var product = getProductDetailsQuery.Handle(new GetProductDetailsDataQuery { ProductId = shoppingCartItem.ProductId });
// TODO: For products with custom calculations, need to use selections on shopping cart item
// as well as custom formula and pricing points from product to calculate the item price.
shoppingCartItem.Price = product.Price;
}
}
GetProductDetailsDataQueryHandler
public class GetProductDetailsDataQueryHandler : IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails>
{
private readonly IAppContext context;
public GetProductDetailsDataQueryHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public ProductDetails Handle(GetProductDetailsDataQuery query)
{
return (from product in context.Products
where product.Id == query.ProductId
select new ProductDetails
{
Id = product.Id,
Name = product.Name,
Price = product.Price
}).FirstOrDefault();
}
}
SetTotalsCommandStrategyHandler
public class SetTotalsCommandStrategyHandler : ICommandStrategyHandler<SetTotalsCommandStrategy>
{
private readonly ICommandStrategyHandler<SetDiscountsCommandStrategy> setDiscountsCommand;
private readonly ICommandStrategyHandler<SetSalesTaxCommandStrategy> setSalesTaxCommand;
public SetTotalsCommandStrategyHandler(
ICommandStrategyHandler<SetDiscountsCommandStrategy> setDiscountsCommand,
ICommandStrategyHandler<SetSalesTaxCommandStrategy> setSalesTaxCommand
)
{
if (setDiscountsCommand == null)
throw new ArgumentNullException("setDiscountsCommand");
if (setSalesTaxCommand == null)
throw new ArgumentNullException("setSalesTaxCommand");
this.setDiscountsCommand = setDiscountsCommand;
this.setSalesTaxCommand = setSalesTaxCommand;
}
public void Handle(SetTotalsCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
// Important: Discounts must be calculated before sales tax to ensure the discount is applied
// to the subtotal before tax is calculated.
setDiscountsCommand.Handle(new SetDiscountsCommandStrategy { ShoppingCart = shoppingCart });
setSalesTaxCommand.Handle(new SetSalesTaxCommandStrategy { ShoppingCart = shoppingCart });
}
}
SetDiscountsCommandStrategyHandler
public class SetDiscountsCommandStrategyHandler : ICommandStrategyHandler<SetDiscountsCommandStrategy>
{
public void Handle(SetDiscountsCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
// TODO: Set discounts according to business rules
foreach (var item in shoppingCart.Items)
{
item.PriceDiscount = 0;
}
shoppingCart.SubtotalDiscounts = 0;
shoppingCart.SalesTaxDiscounts = 0;
shoppingCart.ShippingDiscounts = 0;
}
}
SetSalesTaxCommandStrategyHandler
public class SetSalesTaxCommandStrategyHandler : ICommandStrategyHandler<SetSalesTaxCommandStrategy>
{
public void Handle(SetSalesTaxCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
var postalCode = command.ShoppingCart.ShippingPostalCode;
bool isInCalifornia = !string.IsNullOrEmpty(postalCode) ?
// Matches 90000 to 96200
Regex.IsMatch(postalCode, #"^9(?:[0-5]\d{3}|6[0-1]\d{2}|6200)(?:-?(?:\d{4}))?$") :
false;
if (isInCalifornia)
{
var subtotal = shoppingCart.GetSubtotal();
// Rule for California - charge a flat 7.75% if the zip code is in California
var salesTax = subtotal * 0.0775M;
shoppingCart.SalesTax = salesTax;
}
}
}
Do note that there is no shipping calculation in this workflow. This is primarily because the shipping calculation may depend on external APIs and it may take some time to return. Therefore, I am planning to make the AddToCart workflow a step that runs instantaneously when an item is added and make a CalculateShippingAndTax workflow that happens after the fact that updates the UI again after the totals have been retrieved from their (possibly external) sources, which might take time.
Does this solve the problem? Yes, it does fix the real-world problems I was having when commands need to depend on queries.
However, it feels like this really only separates queries from commands conceptually. Physically, they still depend on one another unless you only look at the IDataCommand and IDataQuery abstractions that only depend on ApplicationDbContext. I am not sure if this is the intent of qujck or not. I am also uncertain if this solves the bigger issue of the design being transferable to CQRS or not, but since it is not something I am planning for I am not that concerned about it.
There are always trade offs to consider between conflicting design principles. The way to resolve it is to look at the underlying reasons behind the principles. In this case, being unable to run a query without running the command is problematic, but being unable to run a command without running the query is generally harmless. As long as there's a way to run the query standalone, I see no reason not to add the query result to the command, especially if done something like this:
QueryResult command()
{
// do command stuff
return query();
}

How add properties to the Vaadin Calendar BasicEvent?

I am implementing the Vaadin 7 Calendar and require to display more
event information than is contained in BasicEvent.
Below is some of the code I am using (events are not being displayed
on calendar):
please can you inform me what I need to add/change?
Thank you Steve...
public class CalEvent extends BasicEvent {
private java.lang.String customer = "";
public CalEvent() {
}
public java.lang.String getCustomer() {
return customer;
}
public void setCustomer(java.lang.String customer) {
this.customer = customer;
}
}
public class EvtProvider extends BasicEventProvider {
public void addEvent(CalEvent event) {
super.addEvent(event);
}
public void removeEvent(Event event) {
super.removeEvent(event);
}
}
public class Mgr {
Mgr() {
cal = new Calendar("My Calendar");
EvtProvider evtProvider = new EvtProvider();
cal.setEventProvider(evtProvider);
List<CalEvent> lst = getCalEvents();
for (CalEvent ev : lst) {
cal.addEvent(ev);
}
}
"more event information than is contained in BasicEvent."
For example?
BasicEvent can display a lot of content in event or Event description.
http://i.stack.imgur.com/uRlN4.png

WP8 - Bind ProgressBar visibility

I have a simple thing to code, i checked other questions but couldn't it yet.
I have an application which loads some data from an xml file retrieved from the web, and then displays it inside a longlistselector.
I did it, it works, now i would like to add an indeterminate progressbar which stays active until I finished the data loading.
I enclosed the progressbar in a stackpanel, before my longlistselector, and i bound its visibility to the function ProgressBarVisibility (see code below).
<phone:PivotItem Header="Status">
<StackPanel>
<ProgressBar Value ="0" IsIndeterminate="True" Visibility="{Binding ProgressBarVisibility}"/>
<phone:LongListSelector Margin="0,0,-12,0" ItemsSource="{Binding PivotOne}">
<phone:LongListSelector.ItemTemplate>
<!-- lots of code here -->
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</StackPanel>
</phone:PivotItem>
In the MainViewModel.cs , that's how i wrote the thing.
using System.Windows;
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
this.PivotOne = new ObservableCollection<ItemViewModel>();
this.PivotTwo = new ObservableCollection<ItemViewModel>();
this.PivotThree = new ObservableCollection<ItemViewModel>();
}
/// <summary>
/// A collection for ItemViewModel objects.
/// </summary>
public ObservableCollection<ItemViewModel> PivotOne { get; private set; }
public ObservableCollection<ItemViewModel> PivotTwo { get; private set; }
public ObservableCollection<ItemViewModel> PivotThree { get; private set; }
private string _detailPageTitle = "Default";
/// <summary>
/// DetailPageTitle ritorna il titolo della pagina di dettaglio. Viene settato nella funzione che carica la pagina secondaria
/// </summary>
/// <returns></returns>
public string DetailPageTitle
{
get
{
return _detailPageTitle;
}
set
{
if (value != _detailPageTitle)
{
_detailPageTitle = value;
NotifyPropertyChanged("DetailPageTitle");
}
}
}
public bool IsDataLoaded
{
get;
private set;
}
private Visibility _progressBarVisibility = Visibility.Collapsed;
public Visibility ProgressBarVisibility
{
get
{
return _progressBarVisibility;
}
set
{
if (value != _progressBarVisibility)
{
_progressBarVisibility = value;
NotifyPropertyChanged("ProgressBarVisibility");
}
}
}
private Visibility _progressBarVisibility = Visibility.Visible;
public Visibility ProgressBarVisibility
{
get
{
return _progressBarVisibility;
}
set
{
if (value != _progressBarVisibility)
{
_progressBarVisibility = value;
NotifyPropertyChanged("ProgressBarVisibility");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public void LoadData()
{
//progressbar is visible, data not loaded
this.IsDataLoaded = false;
ProgressBarVisibility = Visibility.Visible;
// Load Static and dynamic data -- populate the different pivots
LoadStaticData();
LoadXMLFile();
// data loaded, progressbar collapsed
this.IsDataLoaded = true;
ProgressBarVisibility = Visibility.Collapsed;
}
So i included system.windows library, and used the visibility class.
Anyway, i cannot get the progressbar to disappear when the loading is done, it keeps going.
Any suggestion? where am i doing it wrong?
Thanks in advance!
Solution: loaddata is executed on the app activation, so the content is not even rendered at that moment.
Your MainViewModel must implement INotifyPropertyChanged to signal to the View that one of the properties has changed. In addition, when you change the ProgressBarVisibility property, it should fire the PropertyChanged event.
There are a number of MVVM frameworks that come with some implementation of INotifyPropertyChanged, but you could easily implement something simple yourself.
You need to report the changed made to the view:
Change
public Visibility ProgressBarVisibility { get; set; }
by
private Visibility _progressBarVisibility;
public Visibility ProgressBarVisibility
{
get { return _progressBarVisibility;}
set { _progressBarVisibility = value; RaisePropertyChanged("ProgressBarVisibility");}
}
Make sure you implement INotifyPropertyChanged or a base ViewModel that implement it (MVVMLigth : ViewModelBase).

uCommerce - add dynamic property to order line

I have hit a problem building a uCommerce site based on top of the demo razor store available http://thesitedoctor.co.uk/portfolio/avenue-clothingcom/
The demo uses servicestack and the ucommerceapi for its basket functions.
I am trying to add a dynamic property to the basket (on an order line) at the point where the user clicks buy. I traced through the productpage.js file and amended the code to add a new property ('message'):
function (data) {
var variant = data.Variant;
$.uCommerce.addToBasket(
{
sku: variant.Sku,
variantSku: variant.VariantSku,
quantity: qty,
message: $('#personalisedMessage').val()
},
function () {
updateCartTotals(addToCartButton);
}
);
});
using firebug, i checked the data that is being posted
addToExistingLine: true
message: "this is a message"
quantity:"1"
sku: "Product (options: none)"
variantSku:""
Posting this does not cause an error, but I cannot tell if it has worked either - I cannot find it in the database, assuming that it would be stored in OrderProperty table. In this scenario, I am 'buying' a product with no variations.
Any help is greatly appreciated with this.
Out of the box you can't add order/line item properties via the API like that. The API payload that you've added to is specified although valid JSON won't get interpreted/used by the API.
Instead what you'll need to do is add your own method to the API. To do this you'll need to implement a service from IUCommerceApiService and then you can do what you need. I've created an example (untested) below and will get it added to the demo store as I think it's a useful bit of functionality to have.
public class AddOrderLineProperty
{
public int? OrderLineId { get; set; }
public string Sku { get; set; }
public string VariantSku { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class AddOrderLinePropertyResponse : IHasResponseStatus
{
public AddOrderLinePropertyResponse() { }
public AddOrderLinePropertyResponse(UCommerce.EntitiesV2.OrderLine line)
{
if (line == null)
{
UpdatedLine = new LineItem();
return;
}
var currency = SiteContext.Current.CatalogContext.CurrentCatalog.PriceGroup.Currency;
var lineTotal = new Money(line.Total.Value, currency);
UpdatedLine = new LineItem()
{
OrderLineId = line.OrderLineId,
Quantity = line.Quantity,
Sku = line.Sku,
VariantSku = line.VariantSku,
Price = line.Price,
ProductName = line.ProductName,
Total = line.Total,
FormattedTotal = lineTotal.ToString(),
UnitDiscount = line.UnitDiscount,
VAT = line.VAT,
VATRate = line.VATRate
};
}
public ResponseStatus ResponseStatus { get; set; }
public LineItem UpdatedLine { get; set; }
}
public class AddOrderLinePropertyService : ServiceBase<AddOrderLineProperty>, IUCommerceApiService
{
protected override object Run(AddOrderLineProperty request)
{
var orderLineId = request.OrderLineId;
var sku = request.Sku;
var variantSku = request.VariantSku;
var orderLine = findOrderLine(orderLineId, sku, variantSku);
addPropertyToOrderLine(orderLine, request.Key, request.Value);
TransactionLibrary.ExecuteBasketPipeline();
var newLine = findOrderLine(orderLineId, sku, variantSku);
return new AddOrderLinePropertyResponse(newLine);
}
private void addPropertyToOrderLine(OrderLine orderLine, string key, string value)
{
if (orderLine == null)
return;
orderLine[key] = value;
orderLine.Save();
}
private static OrderLine findOrderLine(int? orderLineId, string sku, string variantSku)
{
return orderLineId.HasValue
? getOrderLineByOrderLineId(orderLineId)
: getOrderLineBySku(sku, variantSku);
}
private static OrderLine getOrderLineBySku(string sku, string variantSku)
{
return String.IsNullOrWhiteSpace(variantSku)
? getOrderLines().FirstOrDefault(l => (l.Sku == sku))
: getOrderLines().FirstOrDefault(l => (l.Sku == sku && l.VariantSku == variantSku));
}
private static OrderLine getOrderLineByOrderLineId(int? orderLineId)
{
return getOrderLines().FirstOrDefault(l => l.OrderLineId == orderLineId);
}
private static ICollection<OrderLine> getOrderLines()
{
return TransactionLibrary.GetBasket().PurchaseOrder.OrderLines;
}
}
You'll need to add the new method to uCommerce.jQuery.js as well something like this:
addOrderLineProperty: function (options, onSuccess, onError) {
var defaults = {
orderLineId: 0
};
var extendedOptions = $.extend(defaults, options);
callServiceStack({ AddOrderLineProperty: extendedOptions }, onSuccess, onError);
}
Let me know if you have any issues using it.
Tim

How to get list of customers, jobs, and employeers using Quickbooks QBFC (8.0 SDK)

I have been given the painful task of writing a C# application to sync up employee time entries in a separate database with Quickbooks. Since I'm brand new to QB programming, I'm trying to peform basic tasks, such as getting a list of customers, then jobs for each customer, then employees. I've been reading the SDK documentation, but I'm still a little fuzzy on the details because the examples I'm finding are a little too advanced for me at the moment :-P
To keep things simple, I would like to ask for a code snippet that gives me the list of customers for starters. Here's the code I've got:
QBSessionManager SessionManager = new QBSessionManager();
IMsgSetRequest customerSet = SessionManager.CreateMsgSetRequest("US", 8, 0);
//
// Code to get list of customers here.
//
SessionManager.OpenConnection2("", "New App", ENConnectionType.ctLocalQBD);
SessionManager.BeginSession(string.Empty, ENOpenMode.omDontCare);
IMsgSetResponse Resp = SessionManager.DoRequests(customerSet);
MessageBox.Show(Resp.ToXMLString());
SessionManager.EndSession();
SessionManager.CloseConnection();
Can anyone fill in the "code to get list of customers here" for me? Thank you very much in advance!
Victor
customers.IncludeRetElementList.Add("IsActive");
customers.IncludeRetElementList.Add("ListID");
customers.IncludeRetElementList.Add("EditSequence");
customers.IncludeRetElementList.Add("Name");
customers.IncludeRetElementList.Add("ParentRef");
Only the fields specified in the above list will be returned from QuickBooks - it is very important to use the correct strings in the correct case - no error messages will result if something is wrong. You cannot specify sub-fields (eg, City within an Address block; you must get the entire Address block). For custom fields, you also must specify the OwnerID (use 0 for custom fields that are not private to an application)
customers.IncludeRetElementList.Add("DataExtRet"); //will return non-private and/or private data extension fields depending on the OwnerIDList, below
customers.OwnerIDList.Add("0"); // required for non-private data extn fields
customers.OwnerIDList.Add("Your Appln GUID"); // Use this to get private data extns for the Appln identified by the GUID
Ok, seems like I found the missing piece:
ICustomerQuery customers = customerSet.AppendCustomerQueryRq();
This produces all the data related to each customer, which is a step forward. Parsing the XML for customers should be pretty straightforward, but parsing the individual tasks/jobs for each customer will be laborious, because there are no subnodes for each task - basically you get repeating chunks of XML with all the basic customer info (address, billing address, shipping address, etc.), then this one property called "FullName" which appends a colon to the customer name, followed by the task title (which itself can be followed by another colon with a subtask title, etc.). I'm wondering if there's something clever I can do with the request query to get a better xml response (for instance, specify what properties I want returned, and maybe enforce the creation of subnodes for each task for a given customer)...comments are appreciated.
Adding to Victors, Chili and Hassan's answer. Glad to have stumbled on this question as I myself was struggling with the QBFC examples.
Here is a full set of code that might just help someone down the road. If in the meantime someone could just point me in the direction of some useful documentation...that would be great.
Getting the Employee data to an XML string
public static string EmployeeListXML()
{
QBSessionManager SessionManager = new QBSessionManager();
IMsgSetRequest msgSetReq = SessionManager.CreateMsgSetRequest("US", 8, 0);
IEmployeeQuery employee = msgSetReq.AppendEmployeeQueryRq();
employee.IncludeRetElementList.Add("IsActive");
employee.IncludeRetElementList.Add("ListID");
employee.IncludeRetElementList.Add("EditSequence");
employee.IncludeRetElementList.Add("FirstName");
employee.IncludeRetElementList.Add("LastName");
employee.IncludeRetElementList.Add("SSN");
//employee.IncludeRetElementList.Add("ParentRef");
//employee.IncludeRetElementList.Add("DataExtRet"); //will return non-private and/or private data extension fields depending on the OwnerIDList, below
employee.OwnerIDList.Add("0"); // required for non-private data extn fields
//customers.OwnerIDList.Add("Your Appln GUID"); // Use this to get private data extns for the Appln identified by the GUID
SessionManager.OpenConnection2("", Application.ProductName, ENConnectionType.ctLocalQBD);
//SessionManager.BeginSession(string.Empty, ENOpenMode.omDontCare);
SessionManager.BeginSession(frmMain.QBFileName, ENOpenMode.omDontCare); // I have the filename on frmMain
IMsgSetResponse Resp = SessionManager.DoRequests(msgSetReq);
SessionManager.EndSession();
SessionManager.CloseConnection();
//MessageBox.Show(Resp.ToXMLString());
return Resp.ToXMLString();
}
Putting the XML string into a List of Emplpoyee Objects
public static List<Employee> EmployeeXMLtoList()
{
string sXML = EmployeeListXML();
List<Employee> lstEmp = new List<Employee>();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(sXML);
XmlNodeList parentNode = xmlDoc.GetElementsByTagName("EmployeeRet");
foreach (XmlNode childNode in parentNode)
{
Employee oEmp = new Employee();
oEmp.ListID = childNode.SelectSingleNode("ListID").InnerText;
oEmp.EditSequence = childNode.SelectSingleNode("EditSequence").InnerText;
oEmp.Active = childNode.SelectSingleNode("IsActive").InnerText;
oEmp.FirstName = childNode.SelectSingleNode("FirstName").InnerText;
oEmp.LastName = childNode.SelectSingleNode("LastName").InnerText;
oEmp.SSN = childNode.SelectSingleNode("SSN").InnerText;
lstEmp.Add(oEmp);
}
return lstEmp;
}
Function that return an Employee Object using Linq
public static Employee GetEmployeeObject(string sSSN)
{
Employee oReturn = null;
List<Employee> lstEmployee = EmployeeXMLtoList();
IEnumerable<Employee> lstEmps = from oEmp in lstEmployee
where oEmp.SSN == sSSN
select oEmp;
foreach (var oEmp in lstEmps)
{
oReturn = oEmp;
}
return oReturn;
}
Example of Code
Employee oEmployee = QB.GetEmployeeObject("112-35-8560");
Employee Class
public class Employee
{
private string sEmployeeID;
private string sSSN;
private string sLastName;
private string sFirstName;
private string sAddress1;
private string sAddress2;
private string sCity;
private string sState;
private string sZipCode;
private string sGender;
private string sEthnicity;
private DateTime dDOB;
private string sMaritalStatus;
private int iDependants;
private string sUScitizen;
private decimal iPayRate;
private string sPhone;
private DateTime dHireDate;
private string sEmail;
public Employee() { }
public string EmployeeID
{
get { return sEmployeeID; }
set { sEmployeeID = value; }
}
public string ListID
{
get; set;
}
public string EditSequence
{
get; set;
}
public string Active
{
get; set;
}
public string SSN
{
get { return sSSN; }
set { sSSN = value; }
}
public string LastName
{
get { return sLastName; }
set { sLastName = value; }
}
public string FirstName
{
get { return sFirstName; }
set { sFirstName = value; }
}
public string FullName
{
get { return FirstName + " " + LastName; }
set { }
}
public string Address1
{
get { return sAddress1; }
set { sAddress1 = value; }
}
public string Address2
{
get { return sAddress2; }
set { sAddress2 = value; }
}
public string State
{
get { return sState; }
set { sState = value; }
}
public string City
{
get { return sCity; }
set { sCity = value; }
}
public string ZipCode
{
get { return sZipCode; }
set { sZipCode = value; }
}
public string Gender
{
get { return sGender; }
set { sGender = value; }
}
public string Ethnicity
{
get { return sEthnicity; }
set { sEthnicity = value; }
}
public DateTime DOB
{
get { return dDOB; }
set { dDOB = value; }
}
public string MaritalStatus
{
get { return sMaritalStatus; }
set { sMaritalStatus = value; }
}
public int Dependants
{
get { return iDependants; }
set { iDependants = value; }
}
public string UScitizen
{
get { return sUScitizen; }
set { sUScitizen = value; }
}
public decimal PayRate
{
get { return iPayRate; }
set { iPayRate = value; }
}
public DateTime HireDate
{
get { return dHireDate; }
set { dHireDate = value; }
}
public string Phone
{
get { return sPhone; }
set { sPhone = value; }
}
public string Email
{
get { return sEmail; }
set { sEmail = value; }
}
}

Resources