/// <summary>
        /// When inventory inbound is created, find the inventory item that needs to be fullfilled
        /// and fullfil with item from inventory inbound
        /// </summary>
        /// <param name="db"></param>
        /// <param name="obj"></param>
        internal static void ProcessInventoryInboundCreation(NancyBlackDatabase db, InventoryInbound obj)
        {
            // ensures that only one thread will be doing this
            lock (InventoryAdminModule.LockObject)
            {
                db.Transaction(() =>
                {
                    var inboundItems = new List <InventoryItem>();

                    foreach (var item in obj.Items)
                    {
                        InventoryItem ivitm      = new InventoryItem();
                        ivitm.InboundDate        = obj.InboundDate;
                        ivitm.InventoryInboundId = obj.Id;
                        ivitm.ProductId          = item.ProductId;
                        ivitm.BuyingCost         = item.Price;
                        ivitm.BuyingTax          = item.Tax;

                        db.UpsertRecord(ivitm);

                        inboundItems.Add(ivitm);
                    }

                    InventoryAdminModule.InboundCompleted(db, obj, inboundItems);
                });
            }
        }
Example #2
0
        private void UpdateTag_ObjectUpdate(NancyBlackDatabase db, string table, dynamic obj)
        {
            if (!(obj is IContent))
            {
                return;
            }

            IContent content   = obj;
            var      type      = content.GetType().Name;
            var      existTags = db.Query <Tag>().Where(tag => tag.ContentId == content.Id && tag.Type == type);

            if (content.Tags != null)
            {
                var source = string.Join(",", content.Tags.Split(',').Distinct().OrderBy(s => s)).ToLowerInvariant();
                var inDb   = string.Join(",", existTags.ToList().Select(tag => tag.Name).OrderBy(s => s)).ToLowerInvariant();

                // return when there is nothing change in tags
                if (source == inDb)
                {
                    return;
                }
            }

            db.Transaction(() =>
            {
                // delete exist tags for re-add new tags
                foreach (var tag in existTags)
                {
                    db.DeleteRecord(tag);
                }

                // re-insert tag
                this.InsertTag(db, content);
            });
        }
Example #3
0
        internal static void ProcessReceiptCreation(NancyBlackDatabase db, Receipt obj)
        {
            // When payment receipt is created, create accounting entry
            db.Transaction(() =>
            {
                var saleorder  = db.GetById <SaleOrder>(obj.SaleOrderId);
                var paymentlog = db.GetById <PaymentLog>(obj.PaymentLogId);

                if (saleorder == null || paymentlog == null)
                {
                    // bogus receipt
                    throw new InvalidOperationException("Invalid Receipt was created");
                }

                // Ensures all sale order logic has been ran
                // if the sale order was created before new system change
                if (saleorder.__createdAt < TaxSystemEpoch)
                {
                    saleorder.UpdateSaleOrder(AdminModule.ReadSiteSettings(), db, false);
                }

                // Receipt will create 2 entries
                // 1) PaymentSource account increases, with total amount

                // TODO: Mapping from PaymentSource to Account
                AccountingEntry entry1  = new AccountingEntry();
                entry1.TransactionDate  = paymentlog.__createdAt;
                entry1.TransactionType  = "income";
                entry1.DebtorLoanerName = "Customer";
                entry1.IncreaseAccount  = paymentlog.PaymentSource;
                entry1.IncreaseAmount   = saleorder.TotalAmount;
                entry1.SaleOrderId      = saleorder.Id;

                db.UpsertRecord(entry1);

                if (saleorder.TotalTax > 0)
                {
                    // 2) paid tax is decreased
                    // (ภาษีขาย ทำให้ภาษีซื้อลดลง, ภาษีซื้อ บันทึกไว้ตอน InventoryInbound)
                    AccountingEntry entry2  = new AccountingEntry();
                    entry2.TransactionDate  = paymentlog.__createdAt;
                    entry2.TransactionType  = "expense";
                    entry2.DebtorLoanerName = "Tax";
                    entry2.DecreaseAccount  = "Paid Tax";
                    entry2.DecreaseAmount   = saleorder.TotalTax * -1;
                    entry2.SaleOrderId      = saleorder.Id;

                    db.UpsertRecord(entry2);
                }
            });
        }
Example #4
0
        /// <summary>
        /// Store value to database, only dirty values are stored
        /// </summary>
        /// <param name="db"></param>
        public void Persist(NancyBlackDatabase db)
        {
            lock (this)
            {
                db.Transaction(() =>
                {
                    foreach (var key in _DirtyList)
                    {
                        db.UpsertRecord(_Variables[key]);
                    }
                });

                _DirtyList = new HashSet <string>();
            }
        }
Example #5
0
        public static void SetPackingStatus(NancyBlackDatabase db, SaleOrder so)
        {
            // deduct stock of all product
            // first, we group the product id to minimize selects
            db.Transaction(() =>
            {
                var products = from item in so.Items
                               group item by item into g
                               select g;

                foreach (var productIdGroup in products)
                {
                    var product   = db.GetById <Product>(productIdGroup.Key);
                    product.Stock = product.Stock - productIdGroup.Count();
                    db.UpsertRecord <Product>(product);
                }
            });
        }
Example #6
0
        /// <summary>
        /// Set Receipt Index
        /// </summary>
        /// <param name="db"></param>
        /// <param name="month"></param>
        public static void SetReceiptIdentifier(NancyBlackDatabase db, DateTime month)
        {
            db.Transaction(() =>
            {
                // now, find all payment log of this month

                var startOfMonth = new DateTime(month.Year, month.Month, 1, 0, 0, 0, DateTimeKind.Utc);


                // this is temporary fix for receipt number to be in order as they created
                // because datetime in db are store in utc tick and we cannot use .ToLocalTime() in db.Query().Where() as it unsuported
                // so we use startOfMonth - thaiTimeZone (7 hours) instead
                var thaiTimeZone      = TimeZoneInfo.FindSystemTimeZoneById("SE Asia Standard Time");
                startOfMonth          = startOfMonth.AddTicks(thaiTimeZone.BaseUtcOffset.Ticks * -1);
                var endOfMonth        = startOfMonth.AddMonths(1).AddMilliseconds(-1);
                var paymentsThisMonth = db.Query <PaymentLog>()
                                        .Where(l => l.__createdAt >= startOfMonth && l.__createdAt <= endOfMonth)
                                        .OrderBy(l => l.Id).ToList();

                int counter = 1;
                foreach (var l in paymentsThisMonth)
                {
                    var receipt = db.Query <Receipt>().Where(r => r.PaymentLogId == l.Id).FirstOrDefault();
                    if (receipt == null)
                    {
                        // payment is not successful - so no receipt
                    }
                    else
                    {
                        if (string.IsNullOrWhiteSpace(receipt.Identifier))
                        {
                            // our company is in Thailand so, we only publish doc in Thailand Time
                            var receiptPublishedDate = l.__createdAt.ToUniversalTime().Add(thaiTimeZone.BaseUtcOffset);
                            receipt.Identifier       = receiptPublishedDate.ToString("RCyyyyMM-", System.Globalization.CultureInfo.InvariantCulture) + string.Format("{0:0000}", counter);
                            db.UpsertRecord(receipt);
                        }

                        counter++;
                    }
                }
            });
        }
        /// <summary>
        /// Claim the rewards
        /// </summary>
        /// <param name="db"></param>
        /// <param name="rewardsId"></param>
        /// <param name="registrationId"></param>
        /// <returns></returns>
        public static AffiliateRewardsClaim ClaimReward(NancyBlackDatabase db, int rewardsId, int registrationId)
        {
            var canClaim = AffiliateReward.CanClaim(db, rewardsId, registrationId);

            if (canClaim == false)
            {
                return(null);
            }

            AffiliateReward rewards;
            var             reg = db.GetById <AffiliateRegistration>(registrationId);

            rewards = db.GetById <AffiliateReward>(rewardsId);

            if (rewards.MaxPerUser > 0)
            {
                lock (BaseModule.GetLockObject("RewardClaim-Reg-" + registrationId))
                {
                    var totalClaimedByUser = db.Query <AffiliateRewardsClaim>()
                                             .Where(c => c.AffiliateRewardsId == rewards.Id &&
                                                    c.AffiliateRegistrationId == registrationId).Count();

                    if (totalClaimedByUser >= rewards.MaxPerUser)
                    {
                        return(null);
                    }
                }
            }

            if (rewards.TotalQuota > 0)
            {
                lock (BaseModule.GetLockObject("RewardClaim-" + rewardsId))
                {
                    var totalClaimed = db.Query <AffiliateRewardsClaim>().Where(c => c.AffiliateRewardsId == rewards.Id).Count();
                    rewards.RemainingQuota = rewards.TotalQuota - totalClaimed;
                    db.UpsertRecord(rewards);
                }
            }

            if (rewards.IsRewardsClaimable == false)
            {
                return(null);
            }


            if (rewards.IsCodeDiscount || rewards.IsFreeGiftInSaleOrder)
            {
                var until = DateTime.MaxValue.Ticks;

                if (rewards.CodeDicountExpiryInDays != null)
                {
                    until = DateTime.Now.AddDays(rewards.CodeDicountExpiryInDays.Value).Ticks;
                }
                if (rewards.CodeDiscountExpiryDate != null)
                {
                    until = rewards.CodeDiscountExpiryDate.Value.Ticks;
                }

                AffiliateRewardsClaim claim = null;
                db.Transaction(() =>
                {
                    // free gift also gets created as code

                    Product p = new Product();

                    if (rewards.IsCodeDiscount)
                    {
                        p.Price      = rewards.CodeDiscountAmount * -1;
                        p.Attributes = new
                        {
                            rewardId      = rewards.Id,
                            description   = rewards.Title + ", ราคาก่อนส่วนลดขั้นต่ำ: " + rewards.MinimumPurchaseAmount,
                            min           = rewards.MinimumPurchaseAmount,
                            onetime       = true,
                            until         = until,
                            discount      = rewards.CodeDiscountAmount,
                            affiliateName = reg.AffiliateName,
                            require       = rewards.RequiredProductIds,
                        };
                    }

                    if (rewards.IsFreeGiftInSaleOrder)
                    {
                        p.DiscountPrice    = 0;
                        p.Price            = rewards.CodeDiscountAmount;
                        p.PromotionEndDate = new DateTime(until);
                        p.MasterProductId  = rewards.RewardsProductId;
                        p.IsVariation      = true;

                        p.Attributes = new
                        {
                            rewardId      = rewards.Id,
                            description   = rewards.Title + ", ราคาก่อนส่วนลดขั้นต่ำ: " + rewards.MinimumPurchaseAmount,
                            min           = rewards.MinimumPurchaseAmount,
                            onetime       = true,
                            until         = until,
                            discount      = rewards.CodeDiscountAmount,
                            isfreeproduct = 1,
                            affiliateName = reg.AffiliateName,
                            require       = rewards.RequiredProductIds,
                        };
                    }

                    db.UpsertRecord(p);

                    var code = hashids.Encode(p.Id, reg.Id);
                    p.Url    = "/promotions/code/" + code;
                    p.Title  = "Affiliate Discount: " + code;


                    if (rewards.IsFreeGiftInSaleOrder)
                    {
                        p.Title = "GIFT ITEM:" + rewards.Title;
                    }

                    db.UpsertRecord(p);

                    claim = new AffiliateRewardsClaim();
                    claim.AffiliateRegistrationId = reg.Id;
                    claim.NcbUserId          = reg.NcbUserId;
                    claim.AffiliateCode      = reg.AffiliateCode;
                    claim.DiscountCode       = code;
                    claim.RewardsName        = rewards.Title;
                    claim.AffiliateRewardsId = rewards.Id;
                    claim.ProductId          = p.Id;
                    claim.CouponAttributes   = p.Attributes;
                    db.UpsertRecord(claim);
                });

                return(claim);
            }

            {
                var claim = new AffiliateRewardsClaim();
                claim.AffiliateRegistrationId = reg.Id;
                claim.NcbUserId          = reg.NcbUserId;
                claim.AffiliateCode      = reg.AffiliateCode;
                claim.RewardsName        = rewards.Title;
                claim.AffiliateRewardsId = rewards.Id;
                claim.ProductId          = rewards.RewardsProductId;

                db.UpsertRecord(claim);

                return(claim);
            }
        }
        /// <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);
        }
Example #9
0
        internal static void ProcessReceiptCreation(NancyBlackDatabase db, Receipt obj)
        {
            // When payment receipt is created, create accounting entry
            db.Transaction(() =>
            {
                var saleorder  = db.GetById <SaleOrder>(obj.SaleOrderId);
                var paymentlog = db.GetById <PaymentLog>(obj.PaymentLogId);

                if (paymentlog.Amount <= 0)
                {
                    return; // perhaps it is an error
                }

                if (saleorder == null || paymentlog == null)
                {
                    // bogus receipt
                    throw new InvalidOperationException("Invalid Receipt was created");
                }

                var currentSite = saleorder.SiteSettings;
                if (currentSite == null)
                {
                    currentSite = AdminModule.ReadSiteSettings();
                }

                // Ensures all sale order logic has been ran
                // if the sale order was created before new system change
                if (saleorder.__createdAt < TaxSystemEpoch)
                {
                    saleorder.UpdateSaleOrder(currentSite, db, false);
                }

                // Receipt will create 4 entries
                // 1) PaymentSource account increases, with amount paid

                // TODO: Mapping from PaymentSource to Account
                AccountingEntry entry1  = new AccountingEntry();
                entry1.TransactionDate  = paymentlog.__createdAt;
                entry1.TransactionType  = "income";
                entry1.DebtorLoanerName = "Customer";
                entry1.IncreaseAccount  = paymentlog.PaymentSource;
                entry1.IncreaseAmount   = paymentlog.Amount;
                entry1.SaleOrderId      = saleorder.Id;

                db.UpsertRecord(entry1);

                // 2) Sales Tax Calculation
                {
                    AccountingEntry taxExtry  = new AccountingEntry();
                    taxExtry.TransactionDate  = paymentlog.__createdAt;
                    taxExtry.TransactionType  = "taxcredit";
                    taxExtry.DebtorLoanerName = "Tax";
                    taxExtry.DecreaseAccount  = "Tax Credit";
                    taxExtry.SaleOrderId      = saleorder.Id;

                    if (currentSite.commerce.billing.vattype == "addvat")
                    {
                        var tax = paymentlog.Amount * ((100 + (Decimal)currentSite.commerce.billing.vatpercent) / 100);
                        taxExtry.DecreaseAmount = tax * -1;
                    }

                    if (currentSite.commerce.billing.vattype == "includevat")
                    {
                        var tax = paymentlog.Amount * ((Decimal)currentSite.commerce.billing.vatpercent / 100);
                        taxExtry.DecreaseAmount = tax * -1;
                    }

                    db.UpsertRecord(taxExtry);
                }

                // 3) Payment Fee
                if (paymentlog.Fee > 0)
                {
                    AccountingEntry feeEntry  = new AccountingEntry();
                    feeEntry.TransactionDate  = paymentlog.__createdAt;
                    feeEntry.TransactionType  = "buy";
                    feeEntry.DebtorLoanerName = paymentlog.PaymentSource;
                    feeEntry.IncreaseAccount  = "Payment Fee - " + paymentlog.PaymentSource;
                    feeEntry.IncreaseAmount   = paymentlog.Fee;
                    feeEntry.SaleOrderId      = saleorder.Id;

                    db.UpsertRecord(feeEntry);
                }

                // 4) Receivable from the Sale Order
                {
                    // existing receivable of this sale order
                    var existingReceivable = db.Query <AccountingEntry>().Where(e => e.SaleOrderId == saleorder.Id && e.IncreaseAccount == "Receivable").ToList();

                    // see if we have any receivable of this sale order
                    // if we had, we have to deduct it
                    if (existingReceivable.Count > 0)
                    {
                        AccountingEntry deductReceivableEntry  = new AccountingEntry();
                        deductReceivableEntry.TransactionDate  = paymentlog.__createdAt;
                        deductReceivableEntry.TransactionType  = "arpayment";
                        deductReceivableEntry.DebtorLoanerName = "Receivable From Sales";
                        deductReceivableEntry.DecreaseAccount  = "Receivable";
                        deductReceivableEntry.DecreaseAmount   = paymentlog.Amount;
                        deductReceivableEntry.SaleOrderId      = saleorder.Id;

                        db.UpsertRecord(deductReceivableEntry);
                    }
                    else
                    {
                        // this maybe the first payment, see if all amount has been paid

                        // see all payment log of this sale order
                        // we only query payments up to currently processing payment log
                        // so that when we re
                        var payments  = db.Query <PaymentLog>().Where(l => l.SaleOrderId == saleorder.Id && l.Id <= paymentlog.Id).ToList();
                        var remaining = saleorder.TotalAmount - payments.Sum(p => p.Amount);

                        if (remaining > 0)
                        {
                            // this is partial payment - create new receivable

                            AccountingEntry receivableEntry  = new AccountingEntry();
                            receivableEntry.TransactionDate  = paymentlog.__createdAt;
                            receivableEntry.TransactionType  = "newaccount";
                            receivableEntry.DebtorLoanerName = "Receivable From Sales";
                            receivableEntry.IncreaseAccount  = "Receivable";
                            receivableEntry.IncreaseAmount   = remaining;
                            receivableEntry.SaleOrderId      = saleorder.Id;

                            db.UpsertRecord(receivableEntry);
                        }

                        // this is full payment in one go, no need for receivable
                    }
                }
            });
        }
Example #10
0
        public static void HandlePayment(NancyBlackDatabase db, PaymentLog log, DateTime paidWhen)
        {
            // ensure only one thread is processing this so
            lock (BaseModule.GetLockObject(log.SaleOrderIdentifier))
            {
                db.Transaction(() =>
                {
                    paidWhen = DateTime.SpecifyKind(paidWhen, DateTimeKind.Utc);

                    // find the sale order
                    var so = db.Query <SaleOrder>()
                             .Where(row => row.SaleOrderIdentifier == log.SaleOrderIdentifier)
                             .FirstOrDefault();


                    bool isPaymentReceived = false;

                    JArray exceptions = new JArray();

                    if (so == null)
                    {
                        exceptions.Add(JObject.FromObject(new
                        {
                            type        = "Wrong SO Number",
                            description = "Wrong SO Number"
                        }));

                        goto EndPayment;
                    }

                    log.SaleOrderId = so.Id;
                    log.PaymentDate = paidWhen;

                    // Wrong Payment Status
                    if (so.PaymentStatus == PaymentStatus.PaymentReceived)
                    {
                        so.IsDuplicatePayment = true;
                        exceptions.Add(JObject.FromObject(new
                        {
                            type        = "Wrong Status",
                            description = string.Format(
                                "Current paymentlog status of SO is: {0}", PaymentStatus.DuplicatePayment)
                        }));
                    }

                    // Error code received
                    if (log.IsErrorCode)
                    {
                        so.PaymentStatus = PaymentStatus.WaitingForPayment;
                        exceptions.Add(JObject.FromObject(new
                        {
                            type        = "Error Code",
                            description = "Error Code Received from Payment Processor: " + log.ResponseCode
                        }));

                        goto EndPayment;
                    }


                    var saleOrderPaymentLogs = db.Query <PaymentLog>()
                                               .Where(p => p.SaleOrderIdentifier == so.SaleOrderIdentifier);

                    var totalSuccessful        = saleOrderPaymentLogs.Where(l => l.IsPaymentSuccess).Count();
                    log.SuccessfulPaymentIndex = (totalSuccessful + 1) - 1;

                    // after this line will never be run until EndPayment when IsErrorCode == true
                    if (so.PaymentStatus != PaymentStatus.PaymentReceived && log.Amount != so.TotalAmount)
                    {
                        log.IsPaymentSuccess   = true;
                        so.PaymentStatus       = PaymentStatus.Deposit;
                        so.PaymentReceivedDate = DateTime.Now; // Need to use this to manage queue

                        exceptions.Add(JObject.FromObject(new
                        {
                            type        = "Split Payment",
                            description = string.Format(
                                "Expects: {0} amount from SO, payment is {1}", so.TotalAmount, log.Amount)
                        }));


                        var splitPaymentLogs = (from sPLog in saleOrderPaymentLogs
                                                where sPLog.IsErrorCode == false
                                                select sPLog).ToList();

                        isPaymentReceived = so.TotalAmount <= splitPaymentLogs.Sum(splog => splog.Amount) + log.Amount;
                    }

                    if (exceptions.Count == 0 || isPaymentReceived)
                    {
                        log.IsPaymentSuccess = true;

                        so.PaymentStatus       = PaymentStatus.PaymentReceived;
                        so.PaymentReceivedDate = DateTime.Now;
                    }

                    EndPayment:

                    log.Exception = exceptions;
                    db.UpsertRecord <PaymentLog>(log);

                    CommerceModule.PaymentOccured(so, db);

                    if (log.IsPaymentSuccess)
                    {
                        // Set Receipt number
                        var rc = db.UpsertRecord <Receipt>(new Receipt()
                        {
                            SaleOrderId = so.Id, PaymentLogId = log.Id
                        });
                        db.UpsertRecord(rc);

                        CommerceModule.PaymentSuccess(so, db);
                    }

                    db.UpsertRecord <SaleOrder>(so);

                    // reset the one time code used
                    foreach (var item in so.ItemsDetail)
                    {
                        if (item.Url.StartsWith("/promotions/code/archive-onetime"))
                        {
                            // do nothing
                        }
                        else if (item.Url.StartsWith("/promotions/code"))
                        {
                            if (item.Attributes.onetime != null)
                            {
                                var product = db.GetById <Product>(item.Id);
                                if (product != null)
                                {
                                    product.Url = product.Url.Replace("/promotions/code", "/promotions/code/archive-onetime");
                                    db.UpsertRecord(product);
                                }
                            }
                        }
                    }

                    // Automate change status to WaitingForOrder for add item to PO
                    if (exceptions.Count == 0 || isPaymentReceived)
                    {
                        if (so.Status == SaleOrderStatus.Confirmed)
                        {
                            so.Status = SaleOrderStatus.WaitingForOrder;
                            db.UpsertRecord <SaleOrder>(so);
                        }
                    }
                });


                CommerceModule.SetReceiptIdentifier(db, paidWhen);
            }
        }
Example #11
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="db"></param>
        public void UpdateSaleOrder(dynamic currentSite, NancyBlackDatabase db, bool save = true)
        {
            // Update Total
            this.TotalAmount = 0;

            // Total without discount
            decimal totalWithoutDiscount = 0;

            // snapshot the products into this sale order
            // so that if there is a change in product info later
            // we still have the one that customer sees
            var oldItemsDetail = this.ItemsDetail;

            this.ItemsDetail = new List <Product>();

            //lookupItemDetail is used for provent duplication
            var lookupItemDetail = new Dictionary <int, Product>();

            foreach (var item in this.Items)
            {
                var product = db.GetById <Product>(item);

                // in case some product no longer exist
                if (product == null)
                {
                    var previousProduct = oldItemsDetail.Where(p => p.Id == item).FirstOrDefault();
                    this.ItemsDetail.Add(previousProduct);
                    continue;
                }

                product.ContentParts = null;

                // check for duplication
                if (lookupItemDetail.ContainsKey(product.Id))
                {
                    var     existProduct = lookupItemDetail[product.Id];
                    JObject attr         = existProduct.Attributes;
                    attr["Qty"] = attr.Value <int>("Qty") + 1;
                }
                else
                {
                    JObject attr = product.Attributes;
                    if (attr == null)
                    {
                        attr = new JObject();
                        product.Attributes = attr;
                    }
                    attr["Qty"] = 1;
                    this.ItemsDetail.Add(product);
                    lookupItemDetail.Add(product.Id, product);
                }

                this.TotalAmount     += product.CurrentPrice;
                totalWithoutDiscount += product.Price;
            }

            // insert discount when there are some item with discount price (all in one discount)
            if (this.TotalAmount != totalWithoutDiscount)
            {
                // find all negative prices
                var totalNegativePrices = this.ItemsDetail.Where(i => i.CurrentPrice < 0).Sum(i => i.CurrentPrice);

                var attr = new JObject();
                attr.Add("Qty", 1);
                var discount = new Product()
                {
                    Title      = "Discount",
                    Price      = this.TotalAmount - totalWithoutDiscount,
                    Url        = "/dummy/dummy",
                    Attributes = attr
                };
                this.ItemsDetail.Add(discount);

                this.TotalDiscount        = (discount.Price + totalNegativePrices) * -1;
                this.TotalWithoutDiscount = totalWithoutDiscount + (totalNegativePrices * -1);
            }


            this.SetAllFee(currentSite);

            this.TotalAmount += this.ShippingFee + this.ShippingInsuranceFee + this.PaymentFee;
            this.TotalAmount  = Math.Round(this.TotalAmount, 2, MidpointRounding.AwayFromZero);

            // TAX Calculation
            if (currentSite.commerce.billing.vattype == "addvat")
            {
                this.TotalAmountWithoutTax = this.TotalAmount;
                this.TotalTax    = this.TotalAmountWithoutTax * (100 + (int)currentSite.commerce.billing.vatpercent) / 100;
                this.TotalAmount = this.TotalAmountWithoutTax + this.TotalTax;
            }

            if (currentSite.commerce.billing.vattype == "includevat")
            {
                this.TotalAmountWithoutTax = this.TotalAmount * 100 / (100 + (int)currentSite.commerce.billing.vatpercent);
                this.TotalTax = this.TotalAmount - this.TotalAmountWithoutTax;
            }

            if (!string.IsNullOrEmpty(this.Currency))
            {
                JObject rate = CommerceAdminModule.ExchangeRate;
                decimal want = (decimal)rate.Property(this.Currency).Value;
                decimal home = (decimal)rate.Property("THB").Value;
                this.CurrencyConversionRate = want / home;

                Func <decimal, decimal> toWant = (decimal input) => input * this.CurrencyConversionRate * 1.03m;
                foreach (Product current in this.ItemsDetail)
                {
                    current.Price         = toWant(current.Price);
                    current.DiscountPrice = toWant(current.DiscountPrice);
                }

                this.ShippingFee          = toWant(this.ShippingFee);
                this.ShippingInsuranceFee = toWant(this.ShippingInsuranceFee);
                this.PaymentFee           = toWant(this.PaymentFee);
                this.TotalAmount          = toWant(this.TotalAmount);
            }

            this.SiteSettings = currentSite;

            if (save == false)
            {
                return; // Just update the details for calculation
            }

            db.Transaction(() =>
            {
                // need to insert to get ID
                db.UpsertRecord <SaleOrder>(this);

                this.SaleOrderIdentifier = string.Format(CultureInfo.InvariantCulture,
                                                         "SO{0:yyyyMMdd}-{1:000000}",
                                                         this.__createdAt,
                                                         this.Id);

                // save the SO ID again
                db.UpsertRecord <SaleOrder>(this);
            });
        }
Example #12
0
        /// <summary>
        /// Updates the sale order
        /// </summary>
        /// <param name="db"></param>
        public void UpdateSaleOrder(dynamic currentSite, NancyBlackDatabase db, bool save = true)
        {
            // if we dont have site settings, use the one provided
            // otherwise use the remembered one
            if (this.SiteSettings == null)
            {
                this.SiteSettings = currentSite;
            }
            else
            {
                currentSite = this.SiteSettings;
            }

            // Update Total
            this.TotalAmount = 0;

            // Total without discount
            decimal totalWithoutDiscount = 0;

            // New Logic 2018 - we will primariry use itemsdetail
            // if it already exists - so that admin can add/remove items
            // freely and customer still sees the old prices

            //lookupItemDetail is used for prevent duplication
            var          lookupItemDetail = new Dictionary <int, Product>();
            Action <int> addnewProduct    = (item) =>
            {
                var product = db.GetById <Product>(item);

                if (product == null)
                {
                    return; // id does not exists
                }

                product.ContentParts           = null;
                product.MetaDescription        = null;
                product.MetaKeywords           = null;
                product.Layout                 = null;
                product.PromotionReferenceDate = DateTime.Today;

                // check for duplication
                if (lookupItemDetail.ContainsKey(product.Id))
                {
                    var     existProduct = lookupItemDetail[product.Id];
                    JObject attr         = existProduct.Attributes;
                    attr["Qty"] = attr.Value <int>("Qty") + 1;
                }
                else
                {
                    JObject attr = product.Attributes;
                    if (attr == null)
                    {
                        attr = new JObject();
                        product.Attributes = attr;
                    }
                    attr["Qty"] = 1;
                    this.ItemsDetail.Add(product);
                    lookupItemDetail.Add(product.Id, product);
                }

                this.TotalAmount += product.CurrentPrice;
                if (product.Price > 0)
                {
                    totalWithoutDiscount += product.Price;
                }
            };

            // generate itemsdetail list from items list if not exists
            if (this.ItemsDetail == null || this.ItemsDetail.Count == 0)
            {
                this.ItemsDetail = new List <Product>();

                foreach (var item in this.Items)
                {
                    addnewProduct(item);
                }
            }
            else
            {
                HashSet <int> processedProductId = new HashSet <int>();

                // otherwise - the items list is being generated from the itemsdetail list
                var newItemsList = new List <int>();
                foreach (var item in this.ItemsDetail)
                {
                    processedProductId.Add(item.Id);

                    if (item.Url == "/dummy/dummy")
                    {
                        continue;
                    }

                    if (item.Attributes["Qty"] == null)
                    {
                        continue;
                    }
                    else
                    {
                        for (int i = 0; i < (int)item.Attributes["Qty"]; i++)
                        {
                            // suppose to throw because we cannot verify product price due to cannot specify PromotionReferenceDate
                            if (item.PromotionReferenceDate == default(DateTime))
                            {
                                item.PromotionReferenceDate = this.__createdAt;
                            }

                            newItemsList.Add(item.Id);

                            this.TotalAmount += item.CurrentPrice;

                            if (item.CurrentPrice > item.Price) // this is not discount
                            {
                                totalWithoutDiscount += item.CurrentPrice;
                            }
                            else if (item.CurrentPrice < 0 && item.CurrentPrice == item.Price)
                            {
                                // do nothing
                            }
                            else
                            {
                                totalWithoutDiscount += item.Price;
                            }
                        }
                    }
                }

                foreach (var id in this.Items)
                {
                    if (processedProductId.Contains(id) == false)
                    {
                        addnewProduct(id);
                        newItemsList.Add(id);
                    }
                }

                this.Items = newItemsList.ToArray();
            }

            Func <Product, int> getSortOrder = (p) =>
            {
                var orderList = new string[] {
                    "/laptops/",
                    "/monitor/",
                    "/calibrate/",
                    "/cpu/",
                    "/gpu/",
                    "/ram/",
                    "/m2/",
                    "/hdd/",
                    "/wifi/",
                    "/keyboard/",
                    "/thermal/",
                    "/os/"
                };

                if (p.Url.Contains("/promotion"))
                {
                    return(int.MaxValue);
                }

                for (int i = 0; i < orderList.Length; i++)
                {
                    if (p.Url.IndexOf(orderList[i]) > 0)
                    {
                        return(i);
                    }
                }

                return(int.MaxValue);
            };

            this.ItemsDetail = this.ItemsDetail.OrderBy(p => getSortOrder(p)).ToList();

            // remove the discount item
            var discountItem = this.ItemsDetail.Where(i => i.Url == "/dummy/dummy").FirstOrDefault();

            if (discountItem != null)
            {
                this.ItemsDetail.Remove(discountItem);
            }

            // insert discount when there are some item with discount price (all in one discount)
            if (this.TotalAmount != totalWithoutDiscount)
            {
                // find all negative prices
                var totalNegativePrices = this.ItemsDetail.Where(i => i.CurrentPrice < 0).Sum(i => i.CurrentPrice);

                var attr = new JObject();
                attr.Add("Qty", 1);

                if (this.TotalAmount > totalWithoutDiscount)
                {
                    // discount is more than older amount
                    // this can happen with item that has price = 0
                    discountItem = new Product()
                    {
                        Title      = "Discount",
                        Price      = 0,
                        Url        = "/dummy/dummy",
                        Attributes = attr
                    };
                }
                else
                {
                    discountItem = new Product()
                    {
                        Title      = "Discount",
                        Price      = this.TotalAmount - (totalWithoutDiscount + totalNegativePrices),
                        Url        = "/dummy/dummy",
                        Attributes = attr
                    };
                }

                this.ItemsDetail.Add(discountItem);

                this.TotalDiscount        = (discountItem.Price);
                this.TotalWithoutDiscount = totalWithoutDiscount;
            }
            else
            {
                this.TotalDiscount        = 0;
                this.TotalWithoutDiscount = this.TotalAmount;
            }


            this.SetAllFee();

            this.TotalAmount += this.ShippingFee + this.ShippingInsuranceFee + this.PaymentFee;
            this.TotalAmount  = Math.Round(this.TotalAmount, 2, MidpointRounding.AwayFromZero);

            // TAX Calculation
            if (currentSite.commerce.billing.vattype == "addvat")
            {
                this.TotalAmountWithoutTax = this.TotalAmount;
                this.TotalTax    = this.TotalAmountWithoutTax * (100 + (int)currentSite.commerce.billing.vatpercent) / 100;
                this.TotalAmount = this.TotalAmountWithoutTax + this.TotalTax;
            }

            if (currentSite.commerce.billing.vattype == "includevat")
            {
                this.TotalAmountWithoutTax = this.TotalAmount * 100 / (100 + (int)currentSite.commerce.billing.vatpercent);
                this.TotalTax = this.TotalAmount - this.TotalAmountWithoutTax;
            }


            if (save == false)
            {
                return; // Just update the details for calculation
            }

            db.Transaction(() =>
            {
                // need to insert to get ID
                db.UpsertRecord <SaleOrder>(this);

                this.SaleOrderIdentifier = string.Format(CultureInfo.InvariantCulture,
                                                         "SO{0:yyyyMMdd}-{1:000000}",
                                                         this.__createdAt,
                                                         this.Id);

                // save the SO ID again
                db.UpsertRecord <SaleOrder>(this);
            });
        }
        /// <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);
                }
            });
        }