<?php

namespace Hilco\Models;

use Response;
use Carbon\Carbon;
use Hilco\Shipments\Rate;
use Hilco\SoftDeletes;
use Illuminate\Support\Facades\DB;

/**
 * Hilco\Models\Order
 *
 * @property mixed $id
 * @property mixed $division_id
 * @property mixed $plant_id
 * @property string $invoice_number
 * @property string $web_order_number
 * @property mixed $billto_customersegment_id
 * @property mixed $soldto_customersegment_id
 * @property string $fob
 * @property string $payment_terms
 * @property float $tax_amt
 * @property float $freight_amt
 * @property string $invoice_date
 * @property string $ship_date
 * @property Carrier $carrier
 * @property string $ship_via
 * @property string $customer_po
 * @property string $order_type
 * @property string $shipping_policy
 * @property string $date_created
 * @property string $date_modified
 * @property string $deleted_at
 * @property-read \Illuminate\Database\Eloquent\Collection|\Hilco\Models\OrderItem[] $items
 * @property-read \Illuminate\Database\Eloquent\Collection|\Hilco\Models\OrderMainLine[] $mainLines
 * @property-read mixed $affiliateSales
 * @property-read mixed $id_string
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereDivisionId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order wherePlantId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereInvoiceNumber($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereWebOrderNumber($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereBilltoCustomersegmentId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereSoldtoCustomersegmentId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereFob($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order wherePaymentTerms($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereTaxAmt($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereFreightAmt($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereInvoiceDate($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShipDate($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereCarrier($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShipVia($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereCustomerPo($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereOrderType($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShippingPolicy($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereDateCreated($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereDateModified($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereDeletedAt($value)
 * @mixin \Eloquent
 * @property string $hilco_order_number
 * @property string $order_date
 * @property string $order_due_date
 * @property string $order_status
 * @property string $ship_policy
 * @property integer $salesordertype_id
 * @property integer $salesordersource_id
 * @property string $ship_to_attention
 * @property integer $fob_id
 * @property integer $carrier_id
 * @property integer $paymentterm_id
 * @property string $order_instructions
 * @property string $currency_code
 * @property integer $salesrep_id
 * @property integer $billto_customer_id
 * @property integer $soldto_customer_id
 * @property string $shipping_line1
 * @property string $shipping_line2
 * @property string $shipping_line3
 * @property string $shipping_city
 * @property string $shipping_state
 * @property string $shipping_zip
 * @property string $shipping_country
 * @property string $comment_all
 * @property string $comment_external
 * @property string $comment_internal
 * @property string $comment_display
 * @property float $part_amount
 * @property float $misc_amount
 * @property float $part_discount
 * @property float $order_discount
 * @property float $freight_amount
 * @property float $tax_amount
 * @property float $ins_amount
 * @property float $order_amount
 * @property string $order_source
 * @property string $order_origin
 * @property string $promo_code
 * @property string $state_license
 * @property string $plant
 * @property string $warehouse
 * @property string $bin_location
 * @property string $date_uploaded
 * @property integer $warehouse_id
 * @property integer $binlocation_id
 * @property string $authorization_code
 * @property float $authorized_amount
 * @property string $auth_reference_num
 * @property integer $deliverymethod_id
 * @property integer $delivery_priority
 * @property integer $deliveryterm_id
 * @property integer $customershippingaddress_id
 * @property string $shipping_no
 * @property string $email
 * @property integer $webuser_id
 * @property float $header_discount_amount
 * @property float $header_discount_percent
 * @property string $header_discount_desc
 * @property string $header_discount_code
 * @property float $handling_amount
 * @property integer $commit_sequence
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereHilcoOrderNumber($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereOrderDate($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereOrderDueDate($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereOrderStatus($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShipPolicy($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereSalesordertypeId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereSalesordersourceId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShipToAttention($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereFobId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereCarrierId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order wherePaymenttermId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereOrderInstructions($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereCurrencyCode($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereSalesrepId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereBilltoCustomerId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereSoldtoCustomerId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShippingLine1($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShippingLine2($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShippingLine3($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShippingCity($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShippingState($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShippingZip($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereShippingCountry($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereCommentAll($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereCommentExternal($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereCommentInternal($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereCommentDisplay($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order wherePartAmount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereMiscAmount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order wherePartDiscount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereOrderDiscount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereFreightAmount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereTaxAmount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereInsAmount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereOrderAmount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereOrderSource($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereStateLicense($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order wherePlant($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereWarehouse($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereBinLocation($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereDateUploaded($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereWarehouseId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereBinlocationId($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereAuthorizationCode($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereAuthorizationAmount($value)
 * @method static \Illuminate\Database\Query\Builder|\Hilco\Models\Order whereAuthReferenceNum($value)
 * @property-read \Hilco\Models\Division $division
 * @property-read \Illuminate\Database\Eloquent\Collection|\Hilco\Models\SubscriptionOrderLine[] $partLines
 * @property-read \Illuminate\Database\Eloquent\Collection|\Hilco\Models\OrderMainLine[] $miscLines
 * @property-read \Hilco\Models\Customer $soldToCustomer
 * @property-read \Hilco\Models\Customer $billToCustomer
 * @property-read \Hilco\Models\Plant $orderShipPlant
 * @property-read \Hilco\Models\Warehouse $orderShipWarehouse
 * @property-read \Hilco\Models\BinLocation $orderShipBinLocation
 * @property-read \Hilco\Models\Customer $customer
 * @property-read \Illuminate\Database\Eloquent\Collection|\Hilco\Models\OrderMainLine[] $orderParts
 * @property-read \Hilco\Models\SalesOrderSource $salesOrderSource
 */
class SubscriptionOrder extends UuidModel
{
	protected $table = 'SubscriptionOrders';
	protected $fillable = ['next_order_date'];

    protected $dates = ['next_order_date'];

	public static $subscriptionPeriods = [
	    self::WEEKLY,
        self::BIWEEKLY,
        self::MONTHLY,
        self::BIMONTHLY,
        SELF::QUARTERLY
	];
	
	const WEEKLY = 'weekly';
    const BIWEEKLY = 'biweekly';
    const MONTHLY = 'monthly';
    const BIMONTHLY = 'bimonthly';
    const QUARTERLY = 'quarterly';

	// Order Status Values
	const ACTIVE_STATUS            = 'ACTIVE'; //subscription is currently active and will continued to be ordered
    const INACTIVE_STATUS          = 'INACTIVE'; //subscription is not active, kept around for posterity and reference

    use SoftDeletes;

    //todo this relationship is not defined yet as it is probably unnecessary
//    public function import_SalesOrder(){
//        return $this->hasOne(Import_SalesOrder::class, 'salesorder_id');
//    }

    //todo this relationship is not defined yet as it is probably unnecessary
//    public function authorization(){
//        return $this->hasOne(CreditCardAuthorization::class, 'salesorder_id');
//    }

    public function webSilo() {
        return $this->belongsTo(WebSilo::class, "websilo_id");
    }

	public function fob() {
		return $this->belongsTo(FOB::class, "fob_id");
	}

	public function salesrep() {
		return $this->belongsTo(SalesRep::class, "salesrep_id");
	}

	public function orderedByUser() {
	    return $this->belongsTo(WebUser::class, 'webuser_id', 'id');
    }

    public function approvedByUser() {
        return $this->belongsTo(WebUser::class, 'approver_webuser_id', 'id');
    }

	public function salesOrderSource() {
		return $this->belongsTo(SalesOrderSource::class, "salesordersource_id");
	}

	//todo relationship not defined, probably not needed
//	public function orderLineSchedules()
//	{
//		return $this->hasMany(SalesOrderLineSchedule::class, 'salesorder_id');
//	}

	public function carrier() {
		return $this->belongsTo(Carrier::class, "carrier_id");
	}

	public function deliveryMethodTerm(){
	    return DeliveryMethodTerm::delivery($this->deliverymethod_id, $this->deliveryterm_id, $this->delivery_priority);
    }

	public function deliveryMethod(){
	    return $this->belongsTo(DeliveryMethod::class, "deliverymethod_id");
    }

    public function deliveryTerm(){
	    return $this->belongsTo(DeliveryTerm::class, "deliveryterm_id");
    }
	
	public function division() {
		return $this->belongsTo(Division::class, "division_id");
	}

	public function customer() {
		return $this->belongsTo(Customer::class, 'soldto_customer_id');
	}

	public function paymentTerms() {
	    return $this->belongsTo(PaymentTerm::class, 'paymentterm_id', 'id');
    }

//	public function mainLines()
//	{
//		return $this->hasMany(OrderMainLine::class, 'salesorder_id')
//			->join('SalesOrderPartLines', 'SalesOrderMainLines.parent_id', '=', 'SalesOrderPartLines.id')
//			->join('Parts', 'SalesOrderPartLines.part_id', '=', 'Parts.id')
//			->join('ProductFamily', 'Parts.productfamily_id', '=', 'ProductFamily.id')
//			->join('ProductCategories', 'ProductFamily.productcategory_id', '=', 'ProductCategories.id')
//			->join('ProductCategoryGroups', 'ProductCategories.productcategorygroup_id', '=', 'ProductCategoryGroups.id')
//			->where('SalesOrderMainLines.deleted_at', '=', "0")
////			->groupBy('ProductCategories.product_category')
////			->addSelect('SalesOrderMainLines.*', 'ProductCategories.product_category','ProductCategoryGroups.product_category_group')
////			->addSelect(DB::raw('SUM(SalesOrders.order_amount) as category_total'))
//			;
//	}

	public function partLines() {
	    return $this->hasMany(SubscriptionOrderLine::class, 'subscriptionorder_id')->where('deleted_at', '=', '0000-00-00')->with('part');
	}

	//todo this relationship is not defined yet as it is probably unnecessary
//	public function miscLines() {
//		return $this->hasMany(OrderMainLine::class, 'salesorder_id')->where('parent_type', 'SalesOrderMiscLines')->where('deleted_at', '=', '0000-00-00')->with('miscLines.miscCharge');
//	}
	
	public function soldToCustomer() {
		return $this->belongsTo(Customer::class, "soldto_customer_id");
	}

	public function billToCustomer() {
		return $this->belongsTo(Customer::class, "billto_customer_id");
	}

	public function billToCustomerSegment() {
	    $activeSegments = $this->billToCustomer->activeSegments;
	    if ($activeSegments) {
	        return $activeSegments->take(1);
        } else {
	        return null;
        }
    }

    public function shipToAddress() {
        return $this->belongsTo(CustomerShippingAddress::class, "customershippingaddress_id");
    }

    public function shipToAddressWithTrashed() {
	    return $this->belongsTo(CustomerShippingAddress::class, "customershippingaddress_id")->withTrashed();
    }

	public function orderShipPlant() {
		return $this->belongsTo(Plant::class, 'plant_id');
	}

	public function orderShipWarehouse() {
		return $this->belongsTo(Warehouse::class, 'warehouse_id');
	}

	public function orderShipBinLocation() {
		return $this->belongsTo(BinLocation::class, 'binlocation_id');
	}

    //todo this relationship is not defined yet as it is probably unnecessary
//	public function creditCardAuthorization() {
//	    return $this->hasMany(CreditCardAuthorization::class, 'salesorder_id');
//    }

    //todo this relationship is not defined yet as it is probably unnecessary
//    public function cstEnteredPharmacyInfo() {
//	    return $this->hasOne(CSTEnteredPharmacyInfo::class, 'salesorder_id');
//    }
	
	public function comments() {

		$comments = [];
		if ($this->comment_all != null && $this->comment_all != '') {
			$comments['A'] = $this->comment_all;
		}
		if ($this->comment_external != null && $this->comment_external != '') {
			$comments['E'] = $this->comment_external;
		}
		if ($this->comment_internal != null && $this->comment_internal != '') {
			$comments['I'] = $this->comment_internal;
		}
		if ($this->comment_display != null && $this->comment_display != '') {
			$comments['D'] = $this->comment_display;
		}

		return $comments;
	}

	public function salesOrders(){
        return $this->belongsToMany(Order::class, 'SalesOrder_SubscriptionOrder', 'subscriptionorder_id', 'salesorder_id')
            ->wherePivot('deleted_at', '=', '0000-00-00 00:00:00');
    }

    public function salesOrdersNeedingUpdate(){
        return $this->salesOrders()->wherePivot('needs_update', '=', '1');
    }

    public function dateChanges()
    {
        return $this->hasMany(SubscriptionOrderDateChange::class, 'subscriptionorder_id');
    }

	public function orderParts()
	{
        $orderParts = $this->hasMany(SubscriptionOrderLine::class, 'subscriptionorder_id')
            ->join('Parts', 'SubscriptionOrderLines.part_id', '=', 'Parts.id');

		return $orderParts;
	}

	//todo well this is definitely wrong, do we need it?
	public function scopeSubmitted($query) {
	    return $query->whereNotIn('order_status', ['UNSUBMITTED', 'DISCARDED', 'CANCELED', 'CANCELLED']);
    }

	public function scopeLastOrders($query, $soldToCustomerId, $take) {
	    return $query
            ->where('soldto_customer_id', $soldToCustomerId)
            ->orderBy('order_date', 'desc')
            ->with('orderParts')
            ->take($take);
    }

    public function scopeDateBetween($query, $fromDate, $toDate = false) {
        if ($toDate === false) {
            $query->where('order_date', '>=', $fromDate);
        } else {
            $query->whereBetween('order_date', [$fromDate, $toDate]);
        }
        return $query;
    }

    public function getFriendlyOrderStatusAttribute() {
        return $this->friendlyStatus($this->order_status);
    }

    public static function friendlyStatus($status) {
        switch ($status) {
            case 'CLOSED':
                return 'Closed';
            case 'PENDING_WEB':
                return 'Pending Approval';
            case 'DENIED':
                return 'Approval Denied';
        }

        return 'Open';
    }

    public function getTotalQuantityAttribute() {
        $qty = 0;
        $partLines = $this->partLines;
        if ($partLines) {
            foreach($partLines as $partLine){
                $qty += $partLine->quantity;
            }
        }

        return $qty;
    }

    //todo this attribute is not defined yet as it is probably unnecessary
//    public function getTotalShippedAttribute() {
//        $qtyShipped = 0;
//        if (isset($this->import_SalesOrder)) {
//            foreach ($this->import_SalesOrder->shipmentUnitItems as $shipmentUnitItem) {
//                $qtyShipped += $shipmentUnitItem->qty_shipped;
//            }
//        }
//        return $qtyShipped;
//    }

    public function getTrackingNumbersAttribute() {
        $numbers = [];
        foreach ($this->orderLineSchedules as $lineSchedule) {
            $numbers[$lineSchedule->shipment->tracking_number] = $lineSchedule->shipment->tracking_number;
        }
        return $numbers;
    }

    public function scopeToday($query) {
//        return $query->whereBetween('date_created', [Carbon::today()->startOfDay()->toDateTimeString(), Carbon::today()->endOfDay()->toDateTimeString()]);
        return $query->where('order_date', Carbon::today()->toDateString());
    }

    public function scopeWebOrders($query) {
        return $query->where('order_origin', 'B2B');
    }

    public function scopeCstOrders($query) {
        return $query->where('hilco_order_number', 'RLIKE', '^[HUW]')->where(function ($where) {
            $where->whereNull('order_origin')->orWhere('order_origin', '!=', 'B2B');
        });
    }

    public function getApprovedAttribute() {
	    return (bool) !($this->order_status == 'PENDING_WEB' || $this->order_status == 'DENIED');
    }

    //todo this relationship is not defined yet as it is probably unnecessary
//    public function promotions() {
//	    return $this->belongsToMany(Promotion::class,
//                                    'Promotion_SalesOrder',
//                            'salesorder_id',
//                            'promotion_id')
//                    ->wherePivot('applied', '=', 1);
//    }

    //todo this relationship is not defined yet as it is probably unnecessary
//    public function priceListSalesOrder() {
//	    return $this->hasOne(PriceList_SalesOrder::class, 'salesorder_id');
//    }

    //todo this relationship is not defined yet as it is probably unnecessary
//    public function approvalReasonSalesOrder() {
//        return $this->hasOne(ApprovalReasonSalesOrder::class, 'salesorder_id', 'id');
//    }


    public function scopeActive($query){
        return $query->where('order_status', '=', self::ACTIVE_STATUS);
    }

    public function scopeForActiveUser($query){
        return $query->forUser(auth()->user());
    }

    public function scopeForUser($query, $user){
        if ($user){
            return $query->where('webuser_id', '=', $user->id);
        }else{
            return $query->whereRaw('1=0');
        }
    }

    public function scopeForSubscriptionPeriod($query, $period){
        return $query->where('subscription_period', '=', $period);
    }


    public function getHasBannedProductRestrictionsAttribute() {

        return
            BannedPartsCustomer
                ::where('customer_id', $this->soldto_customer_id)
                ->whereIn('part_id', $this->partLines->pluck('part_id'))
                ->count() > 0
            ||
            BannedPartsState
                ::where('state', $this->shipToAddress->state)
                ->where('country', $this->shipToAddress->country)
                ->whereIn('part_id', $this->partLines->pluck('part_id'))
                ->count() > 0;
    }

    public function getRateAttribute(){
        if ($this->deliveryMethod && $this->deliveryTerm){
            return new Rate($this->deliveryMethod->delivery_method_code, $this->deliveryTerm->delivery_term_code, $this->freight_base_amount, true, $this->freight_discount, $this->tax_amount, !is_null($this->ship_via));
        }else{
            return false;
        }
    }

    public function setNextOrderDateAttribute($date){
        $date = Carbon::parse($date);

        //todo maybe stop them from picking weekend dates?
//        while ($date->dayOfWeek === Carbon::SATURDAY || $date->dayOfWeek === Carbon::SUNDAY){
//            $date = $date->minusDays(1);
//        }

        $is_forward = 0;
        //todo maybe stop them from picking weekend dates?

        //if next_order_date is null, then we don't log date changes.
        if (!is_null($this->next_order_date)){
            if ($date->gt($this->next_order_date)){
                if ($date->gt($this->last_selectable_date)){
                    return Response::json(['success'=>false]);
                }
                $is_forward = 1;
            }
            $this->dateChanges()->save(new SubscriptionOrderDateChange([
                'id' => generateUUID(),
                'old_next_order_date' => $this->next_order_date,
                'new_next_order_date' => $date,
                'is_forward' => $is_forward
            ]));
        }
        //todo do we check for more than two forward pushes here?

        $this->attributes['next_order_date'] = $date;//this calls save() for us, that's cool
        return Response::json(['success'=>true]);
    }

    public function getNextOrderAsDateAttribute(){
        return $this->next_order_date->format('m/d/Y');
    }

    public function getLastSelectableDateAttribute(){
        $date = $this->next_order_date->addDays(30);
        //todo maybe stop them from picking weekend dates?
//        while ($date->dayOfWeek === Carbon::SATURDAY || $date->dayOfWeek === Carbon::SUNDAY){
//            $date = $date->minusDays(1);
//        }
        return $date;
    }

    public function getLastSelectableDateFormattedAttribute(){
        return $this->last_selectable_date->format('m/d/Y');
    }

    /**
     * For anybody following at home, the difference between this function and the one in SubscriptionCart is that this expects a array of SubscriptionOrderLines (and that one expects SubscriptionCarts)
     * 
     * @param $orderLineItems
     * @param $customer
     * @param $webuser
     * @param $useDummy
     * @return array
     * [
     *     part_id => [
     *         'baseprice' => base price of the part from the price list used,
     *         'isContractPrice' => boolean value from PriceLists->contract_flag,
     *         'priceListUsed' => name of the price list that was used,
     *         'totalQuantity' => total quantity of this part in the cart
     *     ]
     * ]
     */
    public static function getDistinctPartInfo($orderLineItems, $customer = null, $webuser = null, $useDummy = true) {
        if ($customer == null) {
            $customer = b2b()->activeCustomer();
        }

        if ($webuser == null) {
            $webuser = auth()->user();
        }

        $distinctPartQuantities = [];
        foreach ($orderLineItems as $orderLineItem) {
            $distinctPartQuantities[$orderLineItem->part->id]['partNumber'] = $orderLineItem->part_number;
            array_increment($distinctPartQuantities[$orderLineItem->part->id],'quantity', $orderLineItem->quantity);
        }

        $distinctLineQuantities = [];

        foreach($orderLineItems as $orderLineItem){
            array_push($distinctLineQuantities, [
                'partId' => $orderLineItem->part->id,
                'partNumber' => $orderLineItem->part_number,
                'quantity' => $orderLineItem->quantity
            ]);
        }

        $distincts = [
            'distinctLineInfo' => [],
            'distinctPartInfo' => []
        ];
        try {
            $distinctPartInfo = SubscriptionCart::fetchAllItemPrices($distinctPartQuantities, false, $customer, $webuser, $useDummy);
            $distinctLineInfo = SubscriptionCart::fetchAllItemPrices($distinctLineQuantities, true, $customer, $webuser, $useDummy);

            $distincts = [
                'distinctLineInfo' => $distinctLineInfo,
                'distinctPartInfo' => $distinctPartInfo
            ];
        }catch(Exception $e){
            Log::error("Exception caught while fetching prices: " . $e->getMessage());
        }

        return $distincts;
    }

    public function updateTotals(){
        $cartStatus = $this->cartStatus();

        $currentLineNumber = 0;
        $totalQuantity = 0;
        $totalPartAmount = 0;
        $totalDiscAmount = 0;
        $totalTaxAmount = 0;

        $cartItems = $cartStatus['items'];
        foreach ($cartItems as $cartItem) {
            $currentLineNumber++;
            if ($cartItem['webCart']->line_number != $currentLineNumber){
                $cartItem['webCart']->line_number = $currentLineNumber;
                $cartItem['webCart']->save();
            }

            $quantity = $cartItem['quantity'];

            $totalQuantity += $quantity;
            $partExtendedPrice = $cartItem['basePrice'] * $quantity;
            $totalPartAmount += $partExtendedPrice;
            $totalTaxAmount += $cartItem['taxAmount'];

            $totalDiscAmount += $cartItem['extendedDiscountAmount'];
        }

        \Log::debug("Order $this->web_order_number now has $totalQuantity items.");
        if ($totalQuantity > 0){
            $trimmedDivisionCode = b2b()->activeDivision($this->soldToCustomer)->division_trimmed;

            //todo notify customers if shipping price changes
            $activeRate = $this->rate;
            $rates = b2b()->rateShop($cartStatus, false, $activeRate, $this->webSilo, $this->soldToCustomer, $this->shipToAddress);
            $firstRate = $rates->first();

            $rates = $rates->keyBy(function ($item) {
                return $item->method();
            });

            if ($activeRate){
                $activeRate = $rates->get($activeRate->method(), $firstRate);
            }else{
                $activeRate = $firstRate;
            }

            $needsFreightWebErrorApproval = false;
            if ($activeRate->isError() &&
                $trimmedDivisionCode !== Division::UK_CODE && $trimmedDivisionCode !== Division::AU_CODE) {
                $needsFreightWebErrorApproval = true;
            }

            //todo when creating the real orders, assign them the proper status
            $orderStatus = self::ACTIVE_STATUS;

//            // If the order also requires some level of microsite approval, set the status to be pending web approval
//            if (!canUser('approve-orders') && array_get($cartStatus, 'requireApproval', false)) {
//
//                $orderStatus = Order::PENDING_WEB_STATUS;
//            } else if ($needsFreightWebErrorApproval) {
//                // otherwise just set to regular pending approval (i.e., needs to be reviewed by a CSR in CST)
//                $orderStatus = Order::PENDING_APPROVAL_STATUS;
//            } else {
//                // else, no approval necessary, set order to pending export to M3
//                $orderStatus = Order::PENDING_STATUS;
//            }

            $shippingBaseRate = $activeRate->baseRate();
            $shippingDiscount = $activeRate->discountAmount();
            $shippingRate = $shippingBaseRate - $shippingDiscount;
            $shippingRate = $shippingRate < 0 ? 0 : $shippingRate;

            $priority = DeliveryMethodTerm::where('delivery_method_code', '=', $activeRate->method())
                ->where('delivery_term_code', '=', $activeRate->term())
                ->where('deleted_at', '=', '0000-00-00 00:00:00')
                ->pluck('def_delivery_priority')
                ->first();
        }else{

            \Log::debug("Order $this->web_order_number now has $totalQuantity items.");

            $activeRate = false;
            $orderStatus = self::INACTIVE_STATUS;
            $shippingBaseRate = 0;
            $shippingDiscount = 0;
            $shippingRate = 0;
            $priority = null;
            $this->next_order_date = null;
        }



        \Log::debug("Setting status to $orderStatus for order with number " . $this->web_order_number);
        $this->order_status = $orderStatus;
        $this->ship_via = $activeRate !== false && $activeRate->isFreightAccount()? $this->shipToAddress->def_ship_via : null;
        $this->deliverymethod_id = $activeRate !== false? $activeRate->methodID() : null;
        $this->deliveryterm_id = $activeRate !== false? $activeRate->termID() : null;
        $this->delivery_priority = $priority;

        $this->part_amount = $totalPartAmount;
        $this->misc_amount = 0;
        $this->part_discount = 0;
        $this->order_discount = $totalDiscAmount;
        $this->freight_amount = $shippingRate;
        $this->freight_base_amount = $shippingBaseRate;
        $this->freight_discount = $shippingDiscount;
        $this->tax_amount = $totalTaxAmount;
        $this->handling_amount = 0;
        $this->ins_amount = 0;
        $this->order_amount = $this->part_amount + $this->misc_amount + $this->freight_amount + $this->tax_amount + $this->handling_amount + $this->ins_amount - $this->part_discount - $this->order_discount;

        $this->save();
        return true;
    }

    /**
     * converts this order to an object of the same format as the "cart status" array, for use with rate-shopping and discount calculation.
     * 
     * @return array
     */
    public function cartStatus(WebSilo $webSilo = null, WebUser $webUser = null, Customer $soldToCustomer = null, $useDummy = true){

        if (is_null($webSilo)) $webSilo = $this->webSilo;
        if (is_null($webUser)) $webUser = $this->orderedByUser;
        if (is_null($soldToCustomer)) $soldToCustomer = $this->soldToCustomer;

        if (is_null($webSilo) || is_null($webUser) || is_null($soldToCustomer)) return [];

        // Initialize 'status' object
        $status = [
            'itemsCount' => 0,
            'quantityCount' => 0,
            'baseTotal' => 0,
            'discountedTotal' => 0,
            'discounts' => [
                'rewards' => 0,
                'customer' => 0,
            ],
            'productCategoryTotals' => [],
            'rewardsUpgradeSpending' => 0,
            'rewardsTier' => rewards()->tier($soldToCustomer),
            'taxAmount' => 0,
            'shippingAmount' => 0,
            'shippingDiscountAmount' => 0,
            'shippingTaxAmount' => 0,
            'requireApproval' => false,
            'priceList' => '',
            'missingPrices' => false,
            'noStock' => false
        ];


        $items = [];

        // get all current items in the web cart - [array of SubscriptionOrderLine objects]
        $orderLineItems = $this->partLines->sortBy('line_number');


        // match part ids to relevant info (base price, total qty, price list, is_contract)
        // From this we get two lists; one with the individual lines, one with the altogether parts.
        if(count($orderLineItems) > 0) {
            $distincts = self::getDistinctPartInfo($orderLineItems, $soldToCustomer, $webUser, $useDummy);

            $distinctPartInfo = $distincts['distinctPartInfo'];
            $distinctLineInfo = $distincts['distinctLineInfo'];

            $atpData = [];
            foreach ($orderLineItems as $orderLineItem) {
                $atpData[] = $orderLineItem->part_number;
            }

            $atps = [];
            try {
                $atps = WebPart::fetchAllItemsStockInfo($atpData, null, $soldToCustomer, $webUser);
                foreach($atps as $atp){
                    if(($atp['status'] == Part::LOW_STOCK && $atp['stock'] == 0) || $atp['status'] == Part::UNKNOWN){
                        $status['noStock'] = true;
                        break;
                    }
                }
            }catch(Exception $e){
                Log::error("Exception caught while fetching stock info: " . $e->getMessage());
            }
        }else{
            $distinctPartInfo = [];
            $distinctLineInfo = [];
            $atps = [];
        }

        // get all "almost qualifying" promotions (used to conditionally show cart and checkout headers)
        $almostQualifyingPromotionsInfo = WebCart::getAlmostQualifiedPromotionsInfo($soldToCustomer, $orderLineItems, $distinctPartInfo);
        $status['almostQualifiedPromotionsInfo'] = $almostQualifyingPromotionsInfo;

        $status['customerTriggeredPromotions'] = WebCart::getCustomerTriggeredPromotions($soldToCustomer, $orderLineItems, $distinctPartInfo);

        // get relevant discounts and other info from all triggered promotions
        $triggeredPromotionsInfo = WebCart::getTriggeredPromotionsInfo($soldToCustomer, $orderLineItems, $distinctPartInfo);

        // set individual promo info objects and lists for use later
        $orderHeaderDiscounts = $triggeredPromotionsInfo['orderHeaderDiscounts'];
        $triggeredPromotions = $triggeredPromotionsInfo['triggeredPromotions'];
        $discountNotes = $triggeredPromotionsInfo['discountNotes'];
        $freeItemActionsInfo = $triggeredPromotionsInfo['freeItemActionsInfo'];

        // get the current cheapest item in the cart that qualifies for a free-item promotion
//        $cheapestItemAndPromo = self::getCheapestWebCartItemsByProduct($freeItemActionsInfo, $subscriptionCartItems, $customerPrices, $siloPrices, $distinctPartQuantities, $priceListNameToUse, $soldToCustomer);
//        $cheapestItem = $cheapestItemAndPromo['cheapestItem'];
//        $freeItemPromoUsed = $cheapestItemAndPromo['promotionUsed'];
//        $hasAppliedFreeItem = false;

        foreach ($orderLineItems as $key=>$orderLineItem) {
            $part = $orderLineItem->part;
            $partInfo = !empty($distinctLineInfo) ? $distinctLineInfo[$key] : [];

            $appliedPromos = [];

            $basePrice = empty($partInfo['basePrice']) ? 0 : $partInfo['basePrice'];
            if($basePrice == trans('messages.temporarily_unavailable')){
                $status['missingPrices'] = true;
            }
            $isContractPrice = empty($partInfo['basePrice']) ? 0 : !empty($partInfo['isContractPrice']) && $partInfo['isContractPrice'] ? 1 : 0;
            $priceListUsed = empty($partInfo['basePrice']) ? null : !empty($partInfo['priceListUsed']) ? $partInfo['priceListUsed'] : null;
            $itemDiscounts = !empty($partInfo['discounts']) ? $partInfo['discounts'] : [];

            $discountPercent = 0;
            $totalDiscAmount = 0;
            $baseDiscountAmount = 0;
            $extendedDiscountAmount = 0;
            $m3AppliedDiscountType = null;
            $m3AppliedDiscountNdx = 1;
            $discounts = [];
            if(!empty($partInfo['basePrice']) && $basePrice != trans('messages.temporarily_unavailable')) {
                $discountedPrice = $basePrice;
                $extendedBasePrice = $basePrice * $orderLineItem->quantity;

                // As long as this is non-contract pricing, calculate all the applicable discounts for the part
                if (!$isContractPrice) {
                    // RIGHT NOW SUBSCRIPTION ORDERS DON'T HAVE PROMOS. THIS SHOULD NEVER TRIGGER, I'M LEAVING IT IN JUST IN CASE WE NEED IT LATER
                    // if there are still free item actions to process..
                    if (count($freeItemActionsInfo)) {
                        // loop thru each action's list of cheapest parts...
                        foreach ($freeItemActionsInfo as $key => $value) {
                            $freeItemAction = $value['freeItemAction'];
                            $cheapestPartList = $value['cheapestPartList'];
                            // if they still have parts to discount...
                            if (count($cheapestPartList)) {
                                foreach ($cheapestPartList as $partId => $numFree) {
                                    // if this part is still on the list to discount...
                                    if ($numFree > 0) {
                                        // and the current item matches...
                                        if ($part->id == $partId) {
                                            // if the number of times this item should be set to free is less than the current
                                            // qty on this cart line, then use that number, otherwise, just use the cart line qty
                                            $numToDiscount = ($numFree < $orderLineItem->quantity) ? $numFree : $orderLineItem->quantity;
                                            // calculate the actual discount percentage for the line to set that number of items as 'free'
                                            $freePercent = round($numToDiscount * 100 * (1 / $orderLineItem->quantity), 2);
                                            // make sure the cart line's discount percentage is capped at 100%
                                            if ($discountPercent + $freePercent >= 100) {
                                                $freePercent = 100 - $discountPercent;
                                            }
                                            // if there is still a discount to be applied..
                                            if ($freePercent !== 0) {
                                                // keep track of current discount percentage
                                                $discountPercent += $freePercent;
                                                // increment the percent, amt, and extAmt values for the promo disc type
                                                $discountAmount = round($extendedBasePrice * ($freePercent / 100), 2);
                                                $totalDiscAmount += $discountAmount;

                                                $appliedPromos[] = [
                                                    'promoActionType' => 'freeItemAction',
                                                    'promoActionId' => $freeItemAction->id,
                                                    'percent' => $freePercent,
                                                    'amount' => $discountAmount
                                                ];

                                                if($totalDiscAmount >= $extendedBasePrice){
                                                    foreach ($itemDiscounts as $discType => $discount) {
                                                        if($discType == 'promotion') {
                                                            array_increment($discounts, "promotion.percent", $freePercent);
                                                            array_increment($discounts, "promotion.baseAmount", round($discountAmount / $orderLineItem->quantity, 2));
                                                            array_increment($discounts, "promotion.extendedAmount", $discountAmount);
                                                            array_increment($discounts, "promotion.isUsed", 1);

                                                            foreach ($discount as $discAttr => $attrValue) {
                                                                if (str_contains($discAttr, 'TX8')) {
                                                                    $discounts['promotion']['m3Type'] = trim($attrValue, ' ');
                                                                    $discounts['promotion']['m3Ndx'] = $discAttr[3];
                                                                }
                                                            }
                                                        }
                                                    }
                                                }

                                                // make sure the promo that had this free item action is updated to 'applied'
                                                $triggeredPromotions = WebCart::updateAppliedPromotions($triggeredPromotions, $freeItemAction->promotion()->first());
                                                // decrement the number on the cheapest part list to keep track...
                                                $cheapestPartList[$partId] = $numFree - $numToDiscount;
                                                // ...and remove the item from the list once they've all been discounted
                                                if ($cheapestPartList[$partId] == 0) {
                                                    unset($cheapestPartList[$partId]);
                                                }
                                                $freeItemActionsInfo[$key] = [
                                                    'freeItemAction' => $freeItemAction,
                                                    'cheapestPartList' => $cheapestPartList
                                                ];
                                            } else {
                                                // otherwise stop calculating for this free item actions part list, it's already at 100%
                                                break;
                                            }
                                        }
                                    } else {
                                        unset($cheapestPartList[$partId]);
                                        $freeItemActionsInfo[$key] = [
                                            'freeItemAction' => $freeItemAction,
                                            'cheapestPartList' => $cheapestPartList
                                        ];
                                    }
                                }
                            } else {
                                unset($freeItemActionsInfo[$key]);
                            }
                        }
                    }

                    if ($totalDiscAmount < $extendedBasePrice) {
                        $totalDiscAmount = 0;
                        $discountPercent = 0;

                        $hasCustomerDiscounts = false;
                        $hasRewardsDiscounts = false;
                        $hasPromotionalDiscounts = false;
                        $hasM3Discounts = false;
                        $hasRewardsExclusion = false;

                        $customerDiscPercent = 0;
                        $customerDiscAmnt = 0;
                        $rewardsDiscPercent = 0;
                        $rewardsDiscAmnt = 0;
                        $promotionalDiscPercent = 0;
                        $promotionalDiscAmnt = 0;
                        $m3DiscPercent = 0;
                        $m3DiscAmnt = 0;

                        foreach ($itemDiscounts as $discType => $discount) {
                            $dPercent = null;
                            $dAmount = null;
                            $dType = null;
                            $m3Type = null;
                            $m3Ndx = 1;
                            foreach($discount as $discAttr => $attrValue){
                                if (str_contains($discAttr, 'DIP')) {
                                    $dPercent = floatval(trim($attrValue, ' '));
                                } else if (str_contains($discAttr, 'DIA')) {
                                    $dAmount = floatval(trim($attrValue, ' '));
                                } else if (str_contains($discAttr, 'TX8')) {
                                    $dType = $discType;
                                    $m3Type = trim($attrValue, ' ');
                                    $m3Ndx = $discAttr[3];
                                }
                            }
                            // keep track of customer and rewards discounts (see if-check after for-loop)
                            if ($dType === 'customer' && ($dPercent > 0 || $dAmount > 0)) {
                                $customerDiscPercent = $dPercent;
                                $customerDiscAmnt = $dAmount;
                                $hasCustomerDiscounts = true;
                            } else if ($dType === 'rewards' && ($dPercent > 0 || $dAmount > 0)) {
                                $rewardsDiscPercent = $dPercent;
                                $rewardsDiscAmnt = $dAmount;
                                $hasRewardsDiscounts = true;
                            } else if ($dType === 'promotion'){
                                $appliedPromos = array_merge($appliedPromos, $part->getApplicableDiscounts($soldToCustomer, $triggeredPromotions, $partInfo));
                                if(!empty($appliedPromos)){
                                    foreach($appliedPromos as $appliedPromo){
                                        $dPercent += $appliedPromo['percent'];
                                        $dAmount += $appliedPromo['amount'];
                                    }

                                    $hasPromotionalDiscounts = true;
                                }else{
                                    $dPercent = 0;
                                    $dAmount = 0;
                                }
                                $promotionalDiscPercent = $dPercent;
                                $promotionalDiscAmnt = $dAmount;
                            }else{
                                $m3DiscPercent += $dPercent;
                                $m3DiscAmnt += $dAmount;
                                $hasM3Discounts = true;
                            }

                            if(!\Illuminate\Support\Arr::has($discounts, $dType)){
                                $discounts[$dType] = [];
                            }

                            array_increment($discounts[$dType], "percent", $dPercent);
                            array_increment($discounts[$dType], "baseAmount", round($dAmount / $orderLineItem->quantity, 2));
                            array_increment($discounts[$dType], "extendedAmount", $dAmount);
                            array_increment($discounts[$dType], "isUsed", 1);
                            $discounts[$dType]['m3Type'] = $m3Type;
                            $discounts[$dType]['m3Ndx'] = $m3Ndx;
                        }

                        // if the item has both customer and rewards discounts, only add the greater of the two, not both
                        if ($hasCustomerDiscounts && $hasRewardsDiscounts) {
                            if ($customerDiscPercent > $rewardsDiscPercent) {
                                $discountPercent += $customerDiscPercent;
                                $totalDiscAmount += $customerDiscAmnt;
                                $discounts['rewards']['isUsed'] = 0;
                                $m3AppliedDiscountType = $discounts['customer']['m3Type'] ;
                                $m3AppliedDiscountNdx =$discounts['customer']['m3Ndx'] ;
                            } else {
                                $discountPercent += $rewardsDiscPercent;
                                $totalDiscAmount += $rewardsDiscAmnt;
                                $discounts['customer']['isUsed'] = 0;
                                $m3AppliedDiscountType = $discounts['rewards']['m3Type'] ;
                                $m3AppliedDiscountNdx =$discounts['rewards']['m3Ndx'] ;
                            }
                        } else if ($hasCustomerDiscounts) {
                            $discountPercent += $customerDiscPercent;
                            $totalDiscAmount += $customerDiscAmnt;
                            $m3AppliedDiscountType = $discounts['customer']['m3Type'] ;
                            $m3AppliedDiscountNdx =$discounts['customer']['m3Ndx'] ;
                        } else if ($hasRewardsDiscounts) {
                            $discountPercent += $rewardsDiscPercent;
                            $totalDiscAmount += $rewardsDiscAmnt;
                            $m3AppliedDiscountType = $discounts['rewards']['m3Type'] ;
                            $m3AppliedDiscountNdx =$discounts['rewards']['m3Ndx'] ;
                        }

                        if ($hasPromotionalDiscounts){
                            $discountPercent += $promotionalDiscPercent;
                            $totalDiscAmount += $promotionalDiscAmnt;
                        }

                        if($hasM3Discounts){
                            $discountPercent += $m3DiscPercent;
                            $totalDiscAmount += $m3DiscAmnt;
                        }
                    }else{
                        foreach ($itemDiscounts as $discType => $discount) {
                            if ($discType != 'promotion') {
                                if(!\Illuminate\Support\Arr::has($discounts, $discType)){
                                    $discounts[$discType] = [];
                                }

                                array_increment($discounts[$discType], "percent", 0);
                                array_increment($discounts[$discType], "baseAmount", 0);
                                array_increment($discounts[$discType], "extendedAmount", 0);
                                array_increment($discounts[$discType], "isUsed", 0);

                                foreach ($discount as $discAttr => $attrValue) {
                                    if (str_contains($discAttr, 'TX8')) {
                                        $discounts[$discType]['m3Type'] = trim($attrValue, ' ');
                                        $discounts[$discType]['m3Ndx'] = $discAttr[3];
                                    }
                                }
                            }
                        }
                    }


                    if ($totalDiscAmount >= $extendedBasePrice){
                        $totalDiscAmount = $extendedBasePrice;
                        $discountPercent = 100;
                    }

                    $baseDiscountAmount = round($totalDiscAmount / $orderLineItem->quantity, 2);
                    $extendedDiscountAmount = $totalDiscAmount;
                    $discountedPrice = round($basePrice - $baseDiscountAmount, 2);
                }
            }else{
                $discountedPrice = 0;
                $extendedBasePrice = 0;
                $baseDiscountAmount = 0;
                $discountPercent = 0;
                $extendedDiscountAmount = 0;
            }

            $extendedPrice = $extendedBasePrice - $extendedDiscountAmount;

            // If applicable, calculate the tax amount
            $taxInfo = [];
            try {
                $taxInfo = WebCart::calculateTaxAmount($orderLineItem->webPart, $extendedPrice, $soldToCustomer, $this->shipToAddress);
            }catch(Exception $e){
//                Log::error("Exception caught while fetching tax info: " . $e->getMessage());
                $taxInfo['isTaxable'] = 0;
                $taxInfo['taxAmount'] = 0;
            }

            // If any part requires approval to be ordered, set the status of the cart to require approval
            $itemRequiresApproval = $orderLineItem->webPart->requireApproval;
            if ($itemRequiresApproval) {
                $status['requireApproval'] = true;
            }

            // Add the part and its relevant details to the $items list
            $items[$orderLineItem->id] = collect([
                'id' => $orderLineItem->id,
                'webCart' => $orderLineItem,//todo does this need to be a webcart?
                'webPart' => $orderLineItem->webPart,
                'atp' => \Illuminate\Support\Arr::get($atps, $orderLineItem->part_number, ['status' => Part::UNKNOWN, 'stock' => 0]),
                'part' => $orderLineItem->part,
                'quantity' => $orderLineItem->quantity,
                'basePrice' => $basePrice,
                'discountedPrice' => $discountedPrice,
                'baseDiscountAmount' => $baseDiscountAmount,
                'discountPercent' => $discountPercent,
                'extendedBasePrice' => $extendedBasePrice,
                'extendedPrice' => $extendedPrice,
                'extendedDiscountAmount' => $extendedDiscountAmount,
                'discounts' => $discounts,
                'appliedPromos' => $appliedPromos,
                'm3DiscountUsed' => $m3AppliedDiscountType,
                'm3DiscountNdx' => $m3AppliedDiscountNdx,
                'isContractPrice' => $isContractPrice,
                'priceListUsed' => $priceListUsed,
                'isTaxable' => $taxInfo['isTaxable'],
                'taxAmount' => $taxInfo['taxAmount'],
                'requireApproval' => $itemRequiresApproval,
            ]);
            array_increment($status, 'itemsCount');
            array_increment($status, 'quantityCount', $orderLineItem->quantity);
            array_increment($status, 'baseTotal', $extendedBasePrice);
            array_increment($status, 'taxAmount', $taxInfo['taxAmount']);

            foreach ($discounts as $discountType => $discountInfo) {
                if (isset($discountInfo['isUsed']) && $discountInfo['isUsed']) {
                    if(in_array($discountType, ['customer', 'rewards', 'promotion'])) {
                        array_increment($status, "discounts.$discountType", $discountInfo['extendedAmount']);
                    }else{
                        array_increment($status, "discounts.hilco", $discountInfo['extendedAmount']);
                    }
                }
            }

            $productCategoryGroup = $part->productFamily->productCategory->productCategoryGroup;
            if(!is_null($productCategoryGroup)){
                array_increment($status, "productCategoryTotals.$productCategoryGroup->id", $extendedPrice);
            }
        }

        // set the ones that won't change and are necessary for other uses in $status
        $status['triggeredPromotions'] = $triggeredPromotions;
        $status['discountNotes'] = $discountNotes;
        $status['orderHeaderDiscounts'] = $orderHeaderDiscounts;

        // Check to see if this order will qualify the active user's customer account for a Rewards Program tier upgrade
        if (rewards()->isRegistered($soldToCustomer) && b2b()->isBillingDirect($soldToCustomer) && rewards()->isEligible($soldToCustomer, false)) {
            $rewardsUpgradeSpending = rewards()->calculateUpgradeSpending($status, $soldToCustomer);
            $status['rewardsUpgradeSpending'] = $rewardsUpgradeSpending;

            // If the order will cause a tier upgrade, recalculate the 'discounts.rewards' discount type in $status
            if ($status['rewardsUpgradeSpending'] < 0) { //Tier Upgrade
                $status['baseRewardsTier'] = $status['rewardsTier'];
                $tierJump = $status['rewardsUpgradeSpending'] * -1;
                $status['rewardsTier'] = min(4, rewards()->tier($soldToCustomer) + $tierJump);
                $status['upgradedTier'] = min(4, rewards()->tier($soldToCustomer) + $tierJump);

                $rewardsDiscountTotal = 0;
                foreach ($items as $itemKey => $item) {
                    if (\Illuminate\Support\Arr::has($item, 'discounts.rewards')) {
                        $discountPercent = rewards()->discountForPartTier($item['part']->id, $status['upgradedTier'], $soldToCustomer);
                        $discountAmount = round($item['basePrice'] * ($discountPercent / 100), 2);
                        $extendedDiscountAmount = $discountAmount * $item['quantity'];
                        $baseDiscountAmount = round($item['basePrice'] * ($discountPercent / 100), 2);
                        $discountedPrice = $item['basePrice'] - $baseDiscountAmount;
                        $extendedPrice = $discountedPrice * $item['quantity'];
                        $rewardsDiscountTotal += $extendedDiscountAmount;
                        //I don't think this is right...this shouldn't be applied until we know for sure whether the reward discounts should be applied

                        /**
                         *  The old method didn't actually add any objects that would be considered in the checkout controller.
                         * It might never have actually worked.
                         * This new method will actually inject the new discount values (replacing them because these are
                         * discounts acquainted with the new upgrade tier.
                         */
                        $discounts = $item->get('discounts');
                        $discounts['rewards']['percent'] = $discountPercent;
                        $discounts['rewards']['baseAmount'] = $discountAmount;
                        $discounts['rewards']['extendedAmount'] = $extendedDiscountAmount;
                        $item->put('discounts', $discounts);

                        // pretty sure these fields are vestigial and unused now, leaving until we clean up web cart someday
                        $item->put('baseDiscountAmount', $discountAmount);
                        $item->put('extendedDiscountAmount', $extendedDiscountAmount);
                        $item->put('discountedPrice', $discountedPrice);
                        $item->put('extendedPrice', $extendedPrice);

                        // m3DiscountUsed is crucial here because it's how CheckoutController::placeOrder() determines
                        // if it should be creating a SalesOrderDiscLine row for the rewards discount on a line item
                        $item->put('m3DiscountUsed', $discounts['rewards']['m3Type']);
                        $item->put('m3DiscountNdx', $discounts['rewards']['m3Ndx']);
                    }
                }
                $status['discounts']['rewards'] = $rewardsDiscountTotal;
            }
        }

        // Calculate any "header-level" discounts applied via promotion(s)
        $calculatedOrderHeaderDiscountMax = 0;
        foreach ($orderHeaderDiscounts as $orderHeadDiscount) {
            $discountMax = \Illuminate\Support\Arr::get($orderHeadDiscount, 'maximum_dollar_amount');
            $calculatedOrderHeaderDiscountMax = max($calculatedOrderHeaderDiscountMax, $discountMax);
        }

        $calculatedOrderHeaderDiscountAmount = 0;
        foreach ($orderHeaderDiscounts as $headerDiscount) {
            $discountPercent = \Illuminate\Support\Arr::get($headerDiscount, 'discount_percent');
            $discountMaxAmount = \Illuminate\Support\Arr::get($headerDiscount, 'maximum_dollar_amount');
            $discountAmount = round($status['baseTotal'] * ($discountPercent/100), 2);
            if ($discountMaxAmount > 0) {
                $discountAmount = min($discountMaxAmount, $discountAmount);
            }
            $calculatedOrderHeaderDiscountAmount += $discountAmount;
        }

        if ($calculatedOrderHeaderDiscountMax > 0) {
            $calculatedOrderHeaderDiscountAmount = min($calculatedOrderHeaderDiscountAmount, $calculatedOrderHeaderDiscountMax);
        }

        array_increment($status, "discounts.promotion", $calculatedOrderHeaderDiscountAmount);

        // Assign 'discountedTotal' to be the combined values of all discount types in $status
        $status['discountedTotal'] = \Illuminate\Support\Arr::get($status, 'baseTotal');
        foreach (\Illuminate\Support\Arr::get($status, 'discounts') as $type => $amt) {
            $status['discountedTotal'] = \Illuminate\Support\Arr::get($status, 'discountedTotal') - $amt;
        }
        if($status['discountedTotal'] < 0 ){
            $status['discountedTotal'] = 0;
        }

        // Rate Shop the order
        $rate = $this->rate;
        if ($rate && !$rate->isError()) {
            $status['shippingAmount'] = $rate->rate();
            $status['shippingDiscountAmount'] = $rate->discountAmount();
            $status['shippingTaxAmount'] = $rate->taxAmount();
        }

        // Get the actual order total (discounted total plus any taxes)
        $status['orderTotal'] = $status['discountedTotal'] + $status['taxAmount'];

        // Final check -- if user is on a microsite that has spending and/or minimum order limits, set the order to
        // require approval if the order violates any of those rules
        $webUserWebSilo = $webUser->webSilo()->where('websilo_id', $this->websilo_id)->first();
        $webCustomerWebSilo = $soldToCustomer->webSilo()->where('id', $this->websilo_id)->first();

        $spendingLimit = -1;
        $minimumOrder = -1;

        if (!b2b()->isAliased()) {
            // if we aren't aliasing, it's safe to look at a webuser's websilo limit settings (even though I'm pretty sure this is kind of deprecated at this point)
            $spendingLimit = \Illuminate\Support\Arr::get($webUserWebSilo, 'pivot.spending_limit', -1);
            $minimumOrder = \Illuminate\Support\Arr::get($webUserWebSilo, 'pivot.minimum_order', -1);
        }

        // if limit settings still aren't set yet, get them from the customer-websilo limit settings
        if($spendingLimit <= 0) $spendingLimit = \Illuminate\Support\Arr::get($webCustomerWebSilo, 'pivot.spending_limit', -1);
        if($minimumOrder <= 0) $minimumOrder = \Illuminate\Support\Arr::get($webCustomerWebSilo, 'pivot.minimum_order', -1);

        // if limit settings STILL aren't set yet, get them from the microsite's defaults
        if($spendingLimit <= 0) $spendingLimit = \Illuminate\Support\Arr::get($webSilo, 'default_spending_limit', -1);
        if($minimumOrder <= 0) $minimumOrder = \Illuminate\Support\Arr::get($webSilo, 'default_minimum_order', -1);

        // finally, if we have limit settings and they're being violated, this order needs approval
        if ($spendingLimit > 0 && $status['discountedTotal'] > $spendingLimit) $status['requireApproval'] = true;
        if ($minimumOrder > 0 && $status['discountedTotal'] < $minimumOrder) $status['requireApproval'] = true;

        $status['items'] = $items;

        return $status;
    }
}
