/// <summary> /// When Saleorder is set to waiting for order, generate InventoryItem for each item in the sale order /// TransformInventoryRequest event is called to finalize the list of items. /// </summary> /// <param name="db"></param> /// <param name="saleOrder"></param> internal static void ProcessSaleOrderUpdate(NancyBlackDatabase db, SaleOrder saleOrder, bool forceUpdate, DateTime now) { if (forceUpdate == false) { now = DateTime.Now; // fulfill the requests of this sale order // (No longer used) //InventoryItemModule.ProcessFulfillSaleOrder(db, saleOrder); // only do when status is waiting for order if (saleOrder.Status != SaleOrderStatus.WaitingForOrder) { return; } // if previous status is already waiting for order - do nothing if (db.GetOlderVersions(saleOrder).Any(s => s.Status == SaleOrderStatus.WaitingForOrder)) { return; } } if (saleOrder.Status == SaleOrderStatus.Cancel) { db.Transaction(() => { var relatedRequest = db.Query <InventoryItem>().Where(ivt => ivt.SaleOrderId == saleOrder.Id).ToList(); foreach (var item in relatedRequest) { // remove the usage from purchase var relatedPurchase = db.Query <InventoryPurchase>().Where(ip => ip.InventoryItemId == item.Id).ToList(); foreach (var p in relatedPurchase) { p.InventoryItemId = 0; db.UpsertRecord(p); } // remove the request from database db.DeleteRecord(item); } }); return; } if (saleOrder.PaymentStatus == PaymentStatus.PaymentReceived || saleOrder.PaymentStatus == PaymentStatus.Deposit || saleOrder.PaymentStatus == PaymentStatus.Credit) { } else { return; } var totalDiscount = 0M; var items = new List <InventoryItem>(); foreach (var item in saleOrder.ItemsDetail) { if (item.CurrentPrice < 0) // dont take negative prices (coupon) { totalDiscount += item.CurrentPrice * -1; // record the discount continue; } if (item.Url == "/dummy/dummy" && item.CurrentPrice == 0) { continue; } // For each items in sale order, create an inventory item for (int i = 0; i < (int)item.Attributes.Qty; i++) { // virtual products does not need inventory inbound if (item.Attributes.IsVirtual == 1) { continue; } var ivitm = new InventoryItem() { SaleOrderId = saleOrder.Id, ProductId = item.Id, RequestedDate = now, IsFullfilled = false, QuotedPrice = item.CurrentPrice, SellingPrice = item.CurrentPrice, }; items.Add(ivitm); if (item.CurrentPrice != item.Price) { totalDiscount += item.Price - item.CurrentPrice; } } } // distribute discount into items which has actual sell price var totalTrueItems = items.Where(item => item.QuotedPrice > 0).Count(); var discountRemaining = totalDiscount; while (discountRemaining > 0) { foreach (var item in items) { if (item.SellingPrice > 0) { // discount by 1% var discount = item.SellingPrice * 0.01M; if (discountRemaining - discount < 0) { discount = discountRemaining; } discountRemaining -= discount; item.SellingPrice = item.SellingPrice - discount; if (discountRemaining == 0) { break; } } } } var currentSite = AdminModule.ReadSiteSettings(); foreach (var item in items) { item.__updatedAt = DateTime.Now; item.__createdAt = DateTime.Now; if (item.SellingPrice > 0) { if (currentSite.commerce.billing.vattype == "addvat") { item.SellingTax = item.SellingPrice * (100 + (int)currentSite.commerce.billing.vatpercent) / 100; } if (currentSite.commerce.billing.vattype == "includevat") { var priceWithoutTax = item.SellingPrice * 100 / (100 + (int)currentSite.commerce.billing.vatpercent); item.SellingTax = item.SellingPrice - priceWithoutTax; item.SellingPrice = priceWithoutTax; } } } InventoryItemModule.TransformInventoryRequest(db, saleOrder, items); // NOTE: sometimes we give free, so price can be 0 items = (from item in items where item.SellingPrice >= 0 select item).ToList(); // attempt to merge the old list and new list using product id // by seeing if there is any item that was already fulfilled - if // there is any - copy the information into new list var existing = db.Query <InventoryItem>().Where(ivt => ivt.SaleOrderId == saleOrder.Id).ToLookup(ivt => ivt.ProductId); var newList = items.ToLookup(ivt => ivt.ProductId); foreach (var group in existing) { var existingGroup = existing[group.Key].ToList(); var newGroup = newList[group.Key].ToList(); for (int i = 0; i < existingGroup.Count; i++) { if (i >= newGroup.Count) { // old list has more items - keep them if it is already fulfilled if (existingGroup[i].IsFullfilled) { existingGroup[i].Note = "This sale order have less items, this is an orphaned row"; db.Connection.Update(existingGroup[i]); } else { // otherwise, just deletes them db.Connection.Delete <InventoryItem>(existingGroup[i].Id); } continue; } if (existingGroup[i].IsFullfilled) { // copy information from the existing one // for some reason not yet understand - i cant just remove newGroup[i] from items // and add existingGroup[i] into it instead! newGroup[i].IsFullfilled = true; newGroup[i].SerialNumber = existingGroup[i].SerialNumber; newGroup[i].FulfilledDate = existingGroup[i].FulfilledDate; newGroup[i].BuyingCost = existingGroup[i].BuyingCost; newGroup[i].BuyingTax = existingGroup[i].BuyingTax; newGroup[i].InventoryPurchaseId = existingGroup[i].InventoryPurchaseId; newGroup[i].Note = existingGroup[i].Note; } db.Connection.Delete <InventoryItem>(existingGroup[i].Id); } } db.Connection.InsertAll(items); }
/// <summary> /// When Saleorder is set to waiting for order, generate InventoryItem for each item in the sale order /// TransformInventoryRequest event is called to finalize the list of items. /// </summary> /// <param name="db"></param> /// <param name="saleOrder"></param> internal static void ProcessSaleOrderUpdate(NancyBlackDatabase db, SaleOrder saleOrder, bool replay, DateTime now) { if (replay == false) { now = DateTime.Now; // only do when status is waiting for order if (saleOrder.Status != SaleOrderStatus.WaitingForOrder) { return; } // if previous status is already waiting for order - do nothing if (db.GetOlderVersions(saleOrder).First().Status == SaleOrderStatus.WaitingForOrder) { return; } } var currentSite = AdminModule.ReadSiteSettings(); // NOTE: We can't run it again since it can alter the amount // it is possible that admin may change amount in database //// ensures that all logic of sale order has been ran //saleOrder.UpdateSaleOrder(currentSite, db, false); // ensure that no inventory inbound can be run var totalDiscount = 0M; var items = new List <InventoryItem>(); foreach (var item in saleOrder.ItemsDetail) { if (item.CurrentPrice < 0) // dont take negative prices (coupon) { totalDiscount += item.CurrentPrice * -1; // record the discount continue; } // For each items in sale order, create an inventory item for (int i = 0; i < (int)item.Attributes.Qty; i++) { var ivitm = new InventoryItem() { SaleOrderId = saleOrder.Id, ProductId = item.Id, RequestedDate = now, IsFullfilled = false, SellingPrice = item.CurrentPrice }; items.Add(ivitm); if (item.CurrentPrice != item.Price) { totalDiscount += item.Price - item.CurrentPrice; } } } // distribute discount into items which has actual sell price var discountToDistribute = totalDiscount / items.Where(item => item.SellingPrice > 0).Count(); // discount is too great for some item, add it to the most expensive one if (items.Where(item => discountToDistribute > item.SellingPrice).Count() > 0) { var item = items.OrderByDescending(i => i.SellingPrice).First(); item.SellingPrice -= totalDiscount; if (currentSite.commerce.billing.vattype == "addvat") { item.SellingTax = item.SellingPrice * (100 + (int)currentSite.commerce.billing.vatpercent) / 100; } if (currentSite.commerce.billing.vattype == "includevat") { var priceWithoutTax = item.SellingPrice * 100 / (100 + (int)currentSite.commerce.billing.vatpercent); item.SellingTax = item.SellingPrice - priceWithoutTax; item.SellingPrice = priceWithoutTax; } } else // distribute it to items { foreach (var item in items) { if (item.SellingPrice > 0) { item.SellingPrice -= discountToDistribute; if (currentSite.commerce.billing.vattype == "addvat") { item.SellingTax = item.SellingPrice * (100 + (int)currentSite.commerce.billing.vatpercent) / 100; } if (currentSite.commerce.billing.vattype == "includevat") { var priceWithoutTax = item.SellingPrice * 100 / (100 + (int)currentSite.commerce.billing.vatpercent); item.SellingTax = item.SellingPrice - priceWithoutTax; item.SellingPrice = priceWithoutTax; } } } } InventoryAdminModule.TransformInventoryRequest(db, saleOrder, items); db.Transaction(() => { // before inserting... // if the inventory item for this sale order already fullfilled // it will remain in inventory but sale order removed // we will always create new inventory item for this sale order // and clear out old ones foreach (var item in db.Query <InventoryItem>().Where(ivt => ivt.SaleOrderId == saleOrder.Id).ToList()) { if (item.IsFullfilled) { item.Note = "Sale Order Id was removed because sale order which created this item has status set to WaitingForOrder Again"; item.SaleOrderId = 0; item.IsFullfilled = false; db.UpsertRecord(item); continue; // item already fullfilled, we leave it but remove sale order id } db.DeleteRecord(item); } foreach (var item in items) { db.UpsertRecord(item); } }); }