Overview

Namespaces

  • emberlabs
    • Barcode
  • None

Classes

  • Appconfig
  • Barcode_lib
  • Config
  • Customer
  • Customer_rewards
  • Customers
  • Detailed_receivings
  • Detailed_sales
  • Dinner_table
  • Email_lib
  • emberlabs\Barcode\BarcodeBase
  • emberlabs\Barcode\Code128
  • emberlabs\Barcode\Code39
  • emberlabs\Barcode\Ean13
  • emberlabs\Barcode\Ean8
  • Employee
  • Employees
  • Giftcard
  • Giftcards
  • Home
  • Inventory
  • Inventory_low
  • Inventory_summary
  • Item
  • Item_kit
  • Item_kit_items
  • Item_kits
  • Item_lib
  • Item_quantity
  • Item_taxes
  • Items
  • Login
  • Mailchimp_lib
  • MailchimpConnector
  • Messages
  • Module
  • MY_Lang
  • No_Access
  • Person
  • Persons
  • Receiving
  • Receiving_lib
  • Receivings
  • Report
  • Reports
  • Rewards
  • Rounding_code
  • Sale
  • Sale_lib
  • Sale_suspended
  • Sales
  • Secure_Controller
  • Sms_lib
  • Specific_customer
  • Specific_discount
  • Specific_employee
  • Stock_location
  • Summary_categories
  • Summary_customers
  • Summary_discounts
  • Summary_employees
  • Summary_items
  • Summary_payments
  • Summary_report
  • Summary_sales
  • Summary_suppliers
  • Summary_taxes
  • Supplier
  • Suppliers
  • Tax
  • Tax_lib
  • Taxes
  • Token
  • Token_customer
  • Token_invoice_count
  • Token_invoice_sequence
  • Token_lib
  • Token_quote_sequence
  • Token_suspended_invoice_count
  • Token_year_invoice_count
  • Tracking_lib

Functions

  • currency_side
  • current_language
  • current_language_code
  • dateformat_bootstrap
  • dateformat_momentjs
  • db_log_queries
  • get_customer_data_row
  • get_customer_manage_table_headers
  • get_giftcard_data_row
  • get_giftcards_manage_table_headers
  • get_item_data_row
  • get_item_kit_data_row
  • get_item_kits_manage_table_headers
  • get_items_manage_table_headers
  • get_people_manage_table_headers
  • get_person_data_row
  • get_sale_data_last_row
  • get_sale_data_row
  • get_sales_manage_payments_summary
  • get_sales_manage_table_headers
  • get_supplier_data_row
  • get_suppliers_manage_table_headers
  • get_tax_data_row
  • get_taxes_manage_table_headers
  • load_config
  • load_language_files
  • load_stats
  • parse_decimals
  • pdf_create
  • quantity_decimals
  • show_report
  • show_report_if_allowed
  • to_currency
  • to_currency_no_money
  • to_decimals
  • to_quantity_decimals
  • to_tax_decimals
  • totals_decimals
  • transform_headers
  • transform_headers_readonly
  • Overview
  • Namespace
  • Class
   1:    2:    3:    4:    5:    6:    7:    8:    9:   10:   11:   12:   13:   14:   15:   16:   17:   18:   19:   20:   21:   22:   23:   24:   25:   26:   27:   28:   29:   30:   31:   32:   33:   34:   35:   36:   37:   38:   39:   40:   41:   42:   43:   44:   45:   46:   47:   48:   49:   50:   51:   52:   53:   54:   55:   56:   57:   58:   59:   60:   61:   62:   63:   64:   65:   66:   67:   68:   69:   70:   71:   72:   73:   74:   75:   76:   77:   78:   79:   80:   81:   82:   83:   84:   85:   86:   87:   88:   89:   90:   91:   92:   93:   94:   95:   96:   97:   98:   99:  100:  101:  102:  103:  104:  105:  106:  107:  108:  109:  110:  111:  112:  113:  114:  115:  116:  117:  118:  119:  120:  121:  122:  123:  124:  125:  126:  127:  128:  129:  130:  131:  132:  133:  134:  135:  136:  137:  138:  139:  140:  141:  142:  143:  144:  145:  146:  147:  148:  149:  150:  151:  152:  153:  154:  155:  156:  157:  158:  159:  160:  161:  162:  163:  164:  165:  166:  167:  168:  169:  170:  171:  172:  173:  174:  175:  176:  177:  178:  179:  180:  181:  182:  183:  184:  185:  186:  187:  188:  189:  190:  191:  192:  193:  194:  195:  196:  197:  198:  199:  200:  201:  202:  203:  204:  205:  206:  207:  208:  209:  210:  211:  212:  213:  214:  215:  216:  217:  218:  219:  220:  221:  222:  223:  224:  225:  226:  227:  228:  229:  230:  231:  232:  233:  234:  235:  236:  237:  238:  239:  240:  241:  242:  243:  244:  245:  246:  247:  248:  249:  250:  251:  252:  253:  254:  255:  256:  257:  258:  259:  260:  261:  262:  263:  264:  265:  266:  267:  268:  269:  270:  271:  272:  273:  274:  275:  276:  277:  278:  279:  280:  281:  282:  283:  284:  285:  286:  287:  288:  289:  290:  291:  292:  293:  294:  295:  296:  297:  298:  299:  300:  301:  302:  303:  304:  305:  306:  307:  308:  309:  310:  311:  312:  313:  314:  315:  316:  317:  318:  319:  320:  321:  322:  323:  324:  325:  326:  327:  328:  329:  330:  331:  332:  333:  334:  335:  336:  337:  338:  339:  340:  341:  342:  343:  344:  345:  346:  347:  348:  349:  350:  351:  352:  353:  354:  355:  356:  357:  358:  359:  360:  361:  362:  363:  364:  365:  366:  367:  368:  369:  370:  371:  372:  373:  374:  375:  376:  377:  378:  379:  380:  381:  382:  383:  384:  385:  386:  387:  388:  389:  390:  391:  392:  393:  394:  395:  396:  397:  398:  399:  400:  401:  402:  403:  404:  405:  406:  407:  408:  409:  410:  411:  412:  413:  414:  415:  416:  417:  418:  419:  420:  421:  422:  423:  424:  425:  426:  427:  428:  429:  430:  431:  432:  433:  434:  435:  436:  437:  438:  439:  440:  441:  442:  443:  444:  445:  446:  447:  448:  449:  450:  451:  452:  453:  454:  455:  456:  457:  458:  459:  460:  461:  462:  463:  464:  465:  466:  467:  468:  469:  470:  471:  472:  473:  474:  475:  476:  477:  478:  479:  480:  481:  482:  483:  484:  485:  486:  487:  488:  489:  490:  491:  492:  493:  494:  495:  496:  497:  498:  499:  500:  501:  502:  503:  504:  505:  506:  507:  508:  509:  510:  511:  512:  513:  514:  515:  516:  517:  518:  519:  520:  521:  522:  523:  524:  525:  526:  527:  528:  529:  530:  531:  532:  533:  534:  535:  536:  537:  538:  539:  540:  541:  542:  543:  544:  545:  546:  547:  548:  549:  550:  551:  552:  553:  554:  555:  556:  557:  558:  559:  560:  561:  562:  563:  564:  565:  566:  567:  568:  569:  570:  571:  572:  573:  574:  575:  576:  577:  578:  579:  580:  581:  582:  583:  584:  585:  586:  587:  588:  589:  590:  591:  592:  593:  594:  595:  596:  597:  598:  599:  600:  601:  602:  603:  604:  605:  606:  607:  608:  609:  610:  611:  612:  613:  614:  615:  616:  617:  618:  619:  620:  621:  622:  623:  624:  625:  626:  627:  628:  629:  630:  631:  632:  633:  634:  635:  636:  637:  638:  639:  640:  641:  642:  643:  644:  645:  646:  647:  648:  649:  650:  651:  652:  653:  654:  655:  656:  657:  658:  659:  660:  661:  662:  663:  664:  665:  666:  667:  668:  669:  670:  671:  672:  673:  674:  675:  676:  677:  678:  679:  680:  681:  682:  683:  684:  685:  686:  687:  688:  689:  690:  691:  692:  693:  694:  695:  696:  697:  698:  699:  700:  701:  702:  703:  704:  705:  706:  707:  708:  709:  710:  711:  712:  713:  714:  715:  716:  717:  718:  719:  720:  721:  722:  723:  724:  725:  726:  727:  728:  729:  730:  731:  732:  733:  734:  735:  736:  737:  738:  739:  740:  741:  742:  743:  744:  745:  746:  747:  748:  749:  750:  751:  752:  753:  754:  755:  756:  757:  758:  759:  760:  761:  762:  763:  764:  765:  766:  767:  768:  769:  770:  771:  772:  773:  774:  775:  776:  777:  778:  779:  780:  781:  782:  783:  784:  785:  786:  787:  788:  789:  790:  791:  792:  793:  794:  795:  796:  797:  798:  799:  800:  801:  802:  803:  804:  805:  806:  807:  808:  809:  810:  811:  812:  813:  814:  815:  816:  817:  818:  819:  820:  821:  822:  823:  824:  825:  826:  827:  828:  829:  830:  831:  832:  833:  834:  835:  836:  837:  838:  839:  840:  841:  842:  843:  844:  845:  846:  847:  848:  849:  850:  851:  852:  853:  854:  855:  856:  857:  858:  859:  860:  861:  862:  863:  864:  865:  866:  867:  868:  869:  870:  871:  872:  873:  874:  875:  876:  877:  878:  879:  880:  881:  882:  883:  884:  885:  886:  887:  888:  889:  890:  891:  892:  893:  894:  895:  896:  897:  898:  899:  900:  901:  902:  903:  904:  905:  906:  907:  908:  909:  910:  911:  912:  913:  914:  915:  916:  917:  918:  919:  920:  921:  922:  923:  924:  925:  926:  927:  928:  929:  930:  931:  932:  933:  934:  935:  936:  937:  938:  939:  940:  941:  942:  943:  944:  945:  946:  947:  948:  949:  950:  951:  952:  953:  954:  955:  956:  957:  958:  959:  960:  961:  962:  963:  964:  965:  966:  967:  968:  969:  970:  971:  972:  973:  974:  975:  976:  977:  978:  979:  980:  981:  982:  983:  984:  985:  986:  987:  988:  989:  990:  991:  992:  993:  994:  995:  996:  997:  998:  999: 1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062: 1063: 1064: 1065: 1066: 1067: 1068: 1069: 1070: 1071: 1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080: 1081: 1082: 1083: 1084: 1085: 1086: 1087: 1088: 1089: 1090: 1091: 1092: 1093: 1094: 1095: 1096: 1097: 1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105: 1106: 1107: 1108: 1109: 1110: 1111: 1112: 1113: 1114: 1115: 1116: 1117: 1118: 1119: 1120: 1121: 1122: 1123: 1124: 1125: 1126: 1127: 1128: 1129: 1130: 1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148: 1149: 1150: 1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162: 1163: 1164: 1165: 1166: 1167: 1168: 1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188: 1189: 1190: 1191: 1192: 1193: 1194: 1195: 1196: 1197: 1198: 1199: 1200: 1201: 1202: 1203: 1204: 1205: 1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214: 1215: 1216: 1217: 1218: 1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 1229: 1230: 1231: 1232: 1233: 1234: 1235: 1236: 1237: 1238: 1239: 1240: 1241: 1242: 1243: 1244: 1245: 1246: 1247: 1248: 1249: 1250: 1251: 1252: 1253: 1254: 1255: 1256: 1257: 1258: 1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269: 1270: 1271: 1272: 1273: 1274: 1275: 1276: 1277: 1278: 1279: 1280: 1281: 1282: 1283: 1284: 
<?php if(!defined('BASEPATH')) exit('No direct script access allowed');

require_once("Secure_Controller.php");

class Sales extends Secure_Controller
{
    public function __construct()
    {
        parent::__construct('sales');

        $this->load->library('sale_lib');
        $this->load->library('tax_lib');
        $this->load->library('barcode_lib');
        $this->load->library('email_lib');
        $this->load->library('token_lib');
    }

    public function index()
    {
        $this->_reload();
    }

    public function manage()
    {
        $person_id = $this->session->userdata('person_id');

        if(!$this->Employee->has_grant('reports_sales', $person_id))
        {
            redirect('no_access/sales/reports_sales');
        }
        else
        {
            $data['table_headers'] = get_sales_manage_table_headers();

            // filters that will be loaded in the multiselect dropdown
            if($this->config->item('invoice_enable') == TRUE)
            {
                $data['filters'] = array('only_cash' => $this->lang->line('sales_cash_filter'),
                    'only_check' => $this->lang->line('sales_check_filter'),
                    'only_invoices' => $this->lang->line('sales_invoice_filter'));
            }
            else
            {
                $data['filters'] = array('only_cash' => $this->lang->line('sales_cash_filter'),
                    'only_check' => $this->lang->line('sales_check_filter'));
            }

            $this->load->view('sales/manage', $data);
        }
    }

    public function get_row($row_id)
    {
        $sale_info = $this->Sale->get_info($row_id)->row();
        $data_row = $this->xss_clean(get_sale_data_row($sale_info, $this));

        echo json_encode($data_row);
    }

    public function search()
    {
        $search = $this->input->get('search');
        $limit = $this->input->get('limit');
        $offset = $this->input->get('offset');
        $sort = $this->input->get('sort');
        $order = $this->input->get('order');

        $filters = array('sale_type' => 'all',
                         'location_id' => 'all',
                         'start_date' => $this->input->get('start_date'),
                         'end_date' => $this->input->get('end_date'),
                         'only_cash' => FALSE,
                         'only_check' => FALSE,
                         'only_invoices' => $this->config->item('invoice_enable') && $this->input->get('only_invoices'),
                         'is_valid_receipt' => $this->Sale->is_valid_receipt($search));

        // check if any filter is set in the multiselect dropdown
        $filledup = array_fill_keys($this->input->get('filters'), TRUE);
        $filters = array_merge($filters, $filledup);

        $sales = $this->Sale->search($search, $filters, $limit, $offset, $sort, $order);
        $total_rows = $this->Sale->get_found_rows($search, $filters);
        $payments = $this->Sale->get_payments_summary($search, $filters);
        $payment_summary = $this->xss_clean(get_sales_manage_payments_summary($payments, $sales, $this));

        $data_rows = array();
        foreach($sales->result() as $sale)
        {
            $data_rows[] = $this->xss_clean(get_sale_data_row($sale, $this));
        }

        if($total_rows > 0)
        {
            $data_rows[] = $this->xss_clean(get_sale_data_last_row($sales, $this));
        }

        echo json_encode(array('total' => $total_rows, 'rows' => $data_rows, 'payment_summary' => $payment_summary));
    }

    public function item_search()
    {
        $suggestions = array();
        $receipt = $search = $this->input->get('term') != '' ? $this->input->get('term') : NULL;

        if($this->sale_lib->get_mode() == 'return' && $this->Sale->is_valid_receipt($receipt))
        {
            // if a valid receipt or invoice was found the search term will be replaced with a receipt number (POS #)
            $suggestions[] = $receipt;
        }
        $suggestions = array_merge($suggestions, $this->Item->get_search_suggestions($search, array('search_custom' => FALSE, 'is_deleted' => FALSE), TRUE));
        $suggestions = array_merge($suggestions, $this->Item_kit->get_search_suggestions($search));

        $suggestions = $this->xss_clean($suggestions);

        echo json_encode($suggestions);
    }

    public function suggest_search()
    {
        $search = $this->input->post('term') != '' ? $this->input->post('term') : NULL;

        $suggestions = $this->xss_clean($this->Sale->get_search_suggestions($search));

        echo json_encode($suggestions);
    }

    public function select_customer()
    {
        $customer_id = $this->input->post('customer');
        if($this->Customer->exists($customer_id))
        {
            $this->sale_lib->set_customer($customer_id);
            $discount_percent = $this->Customer->get_info($customer_id)->discount_percent;

            // apply customer default discount to items that have 0 discount
            if($discount_percent != '')
            {
                $this->sale_lib->apply_customer_discount($discount_percent);
            }
        }

        $this->_reload();
    }

    public function change_mode()
    {
        $stock_location = $this->input->post('stock_location');
        if(!$stock_location || $stock_location == $this->sale_lib->get_sale_location())
        {
            $mode = $this->input->post('mode');
            $this->sale_lib->set_mode($mode);
            $dinner_table = $this->input->post('dinner_table');
            $this->sale_lib->set_dinner_table($dinner_table);
        }
        elseif($this->Stock_location->is_allowed_location($stock_location, 'sales'))
        {
            $this->sale_lib->set_sale_location($stock_location);
        }

        $this->_reload();
    }

    public function set_comment()
    {
        $this->sale_lib->set_comment($this->input->post('comment'));
    }

    public function set_invoice_number()
    {
        $this->sale_lib->set_invoice_number($this->input->post('sales_invoice_number'));
    }

    public function set_invoice_number_enabled()
    {
        $this->sale_lib->set_invoice_number_enabled($this->input->post('sales_invoice_number_enabled'));
    }

    public function set_payment_type()
    {
        $this->sale_lib->set_payment_type($this->input->post('selected_payment_type'));
        $this->_reload();
    }

    public function set_print_after_sale()
    {
        $this->sale_lib->set_print_after_sale($this->input->post('sales_print_after_sale'));
    }

    public function set_email_receipt()
    {
        $this->sale_lib->set_email_receipt($this->input->post('email_receipt'));
    }

    // Multiple Payments
    public function add_payment()
    {
        $data = array();

        $payment_type = $this->input->post('payment_type');
        if($payment_type != $this->lang->line('sales_giftcard'))
        {
            $this->form_validation->set_rules('amount_tendered', 'lang:sales_amount_tendered', 'trim|required|callback_numeric');
        }
        else
        {
            $this->form_validation->set_rules('amount_tendered', 'lang:sales_amount_tendered', 'trim|required');
        }
        if($this->form_validation->run() == FALSE)
        {
            if($payment_type == $this->lang->line('sales_giftcard'))
            {
                $data['error'] = $this->lang->line('sales_must_enter_numeric_giftcard');
            }
            else
            {
                $data['error'] = $this->lang->line('sales_must_enter_numeric');
            }
        }
        else
        {
            if($payment_type == $this->lang->line('sales_giftcard'))
            {
                // in case of giftcard payment the register input amount_tendered becomes the giftcard number
                $giftcard_num = $this->input->post('amount_tendered');

                $payments = $this->sale_lib->get_payments();
                $payment_type = $payment_type . ':' . $giftcard_num;
                $current_payments_with_giftcard = isset($payments[$payment_type]) ? $payments[$payment_type]['payment_amount'] : 0;
                $cur_giftcard_value = $this->Giftcard->get_giftcard_value($giftcard_num);
                $cur_giftcard_customer = $this->Giftcard->get_giftcard_customer($giftcard_num);
                $customer_id = $this->sale_lib->get_customer();
                if(isset($cur_giftcard_customer) && $cur_giftcard_customer != $customer_id)
                {
                    $data['error'] = $this->lang->line('giftcards_cannot_use', $giftcard_num);
                }
                elseif(($cur_giftcard_value - $current_payments_with_giftcard) <= 0)
                {
                    $data['error'] = $this->lang->line('giftcards_remaining_balance', $giftcard_num, to_currency($cur_giftcard_value));
                }
                else
                {
                    $new_giftcard_value = $this->Giftcard->get_giftcard_value($giftcard_num) - $this->sale_lib->get_amount_due();
                    $new_giftcard_value = $new_giftcard_value >= 0 ? $new_giftcard_value : 0;
                    $this->sale_lib->set_giftcard_remainder($new_giftcard_value);
                    $new_giftcard_value = str_replace('$', '\$', to_currency($new_giftcard_value));
                    $data['warning'] = $this->lang->line('giftcards_remaining_balance', $giftcard_num, $new_giftcard_value);
                    $amount_tendered = min($this->sale_lib->get_amount_due(), $this->Giftcard->get_giftcard_value($giftcard_num));

                    $this->sale_lib->add_payment($payment_type, $amount_tendered);
                }
            }
            elseif($payment_type == $this->lang->line('sales_rewards'))
            {
                $customer_id = $this->sale_lib->get_customer();
                $package_id = $this->Customer->get_info($customer_id)->package_id;
                if(!empty($package_id))
                {
                    $package_name = $this->Customer_rewards->get_name($package_id);
                    $points = $this->Customer->get_info($customer_id)->points;
                    $points = ($points==NULL ? 0 : $points);

                    $payments = $this->sale_lib->get_payments();
                    $payment_type = $payment_type;
                    $current_payments_with_rewards = isset($payments[$payment_type]) ? $payments[$payment_type]['payment_amount'] : 0;
                    $cur_rewards_value = $points;

                    if(($cur_rewards_value - $current_payments_with_rewards) <= 0)
                    {
                        $data['error'] = $this->lang->line('rewards_remaining_balance') . to_currency($cur_rewards_value);
                    }
                    else
                    {
                        $new_reward_value = $points - $this->sale_lib->get_amount_due();
                        $new_reward_value = $new_reward_value >= 0 ? $new_reward_value : 0;
                        $this->sale_lib->set_rewards_remainder($new_reward_value);
                        $new_reward_value = str_replace('$', '\$', to_currency($new_reward_value));
                        $data['warning'] = $this->lang->line('rewards_remaining_balance'). $new_reward_value;
                        $amount_tendered = min($this->sale_lib->get_amount_due(), $points);

                        $this->sale_lib->add_payment($payment_type, $amount_tendered);
                    }
                }
            }
            else
            {
                $amount_tendered = $this->input->post('amount_tendered');
                $this->sale_lib->add_payment($payment_type, $amount_tendered);
            }
        }

        $this->_reload($data);
    }

    // Multiple Payments
    public function delete_payment($payment_id)
    {
        $this->sale_lib->delete_payment($payment_id);

        $this->_reload();
    }

    public function add()
    {
        $data = array();

        $discount = 0;

        // check if any discount is assigned to the selected customer
        $customer_id = $this->sale_lib->get_customer();
        if($customer_id != -1)
        {
            // load the customer discount if any
            $discount_percent = $this->Customer->get_info($customer_id)->discount_percent;
            if($discount_percent != '')
            {
                $discount = $discount_percent;
            }
        }

        // if the customer discount is 0 or no customer is selected apply the default sales discount
        if($discount == 0)
        {
            $discount = $this->config->item('default_sales_discount');
        }

        $mode = $this->sale_lib->get_mode();
        $quantity = ($mode == 'return') ? -1 : 1;
        $item_location = $this->sale_lib->get_sale_location();
        $item_id_or_number_or_item_kit_or_receipt = $this->input->post('item');

        if($mode == 'return' && $this->Sale->is_valid_receipt($item_id_or_number_or_item_kit_or_receipt))
        {
            $this->sale_lib->return_entire_sale($item_id_or_number_or_item_kit_or_receipt);
        }
        elseif($this->Item_kit->is_valid_item_kit($item_id_or_number_or_item_kit_or_receipt))
        {
            // Add kit item to order if one is assigned
            $pieces = explode(' ', $item_id_or_number_or_item_kit_or_receipt);
            $item_kit_id = $pieces[1];
            $item_kit_info = $this->Item_kit->get_info($item_kit_id);
            $kit_item_id = $item_kit_info->kit_item_id;
            $price_option = $item_kit_info->price_option;
            $stock_type = $item_kit_info->stock_type;
            $kit_print_option = $item_kit_info->print_option; // 0-all, 1-priced, 2-kit-only

            if($item_kit_info->kit_discount_percent != 0 && $item_kit_info->kit_discount_percent > $discount)
            {
                $discount = $item_kit_info->kit_discount_percent;
            }

            $price = NULL;
            $print_option = 0; // Always include in list of items on invoice

            if(!empty($kit_item_id))
            {
                if(!$this->sale_lib->add_item($kit_item_id, $quantity, $item_location, $discount, $price, NULL, NULL, NULL, $print_option, $stock_type))
                {
                    $data['error'] = $this->lang->line('sales_unable_to_add_item');
                }
                else
                {
                    $data['warning'] = $this->sale_lib->out_of_stock($item_kit_id, $item_location);
                }
            }

            // Add item kit items to order
            $stock_warning = NULL;
            if(!$this->sale_lib->add_item_kit($item_id_or_number_or_item_kit_or_receipt, $item_location, $discount, $price_option, $kit_print_option, $stock_warning))
            {
                $data['error'] = $this->lang->line('sales_unable_to_add_item');
            }
            elseif($stock_warning != NULL)
            {
                $data['warning'] = $stock_warning;
            }
        }
        else
        {
            if(!$this->sale_lib->add_item($item_id_or_number_or_item_kit_or_receipt, $quantity, $item_location, $discount))
            {
                $data['error'] = $this->lang->line('sales_unable_to_add_item');
            }
            else
            {
                $data['warning'] = $this->sale_lib->out_of_stock($item_id_or_number_or_item_kit_or_receipt, $item_location);
            }
        }
        $this->_reload($data);
    }

    public function edit_item($item_id)
    {
        $data = array();

        $this->form_validation->set_rules('price', 'lang:items_price', 'required|callback_numeric');
        $this->form_validation->set_rules('quantity', 'lang:items_quantity', 'required|callback_numeric');
        $this->form_validation->set_rules('discount', 'lang:items_discount', 'required|callback_numeric');

        $description = $this->input->post('description');
        $serialnumber = $this->input->post('serialnumber');
        $price = parse_decimals($this->input->post('price'));
        $quantity = parse_decimals($this->input->post('quantity'));
        $discount = parse_decimals($this->input->post('discount'));
        $item_location = $this->input->post('location');

        if($this->form_validation->run() != FALSE)
        {
            $this->sale_lib->edit_item($item_id, $description, $serialnumber, $quantity, $discount, $price);
        }
        else
        {
            $data['error'] = $this->lang->line('sales_error_editing_item');
        }

        $data['warning'] = $this->sale_lib->out_of_stock($this->sale_lib->get_item_id($item_id), $item_location);

        $this->_reload($data);
    }

    public function delete_item($item_number)
    {
        $this->sale_lib->delete_item($item_number);

        $this->_reload();
    }

    public function remove_customer()
    {
        $this->sale_lib->clear_giftcard_remainder();
        $this->sale_lib->clear_rewards_remainder();
        $this->sale_lib->delete_payment($this->lang->line('sales_rewards'));
        $this->sale_lib->clear_invoice_number();
        $this->sale_lib->clear_quote_number();
        $this->sale_lib->remove_customer();

        $this->_reload();
    }

    public function complete_receipt()
    {
        $this->complete();
    }

    public function complete()
    {
        $data = array();
        $data['dinner_table'] = $this->sale_lib->get_dinner_table();
        $data['cart'] = $this->sale_lib->get_cart();
        $data['receipt_title'] = $this->lang->line('sales_receipt');
        $data['transaction_time'] = date($this->config->item('dateformat') . ' ' . $this->config->item('timeformat'));
        $data['transaction_date'] = date($this->config->item('dateformat'));
        $data['show_stock_locations'] = $this->Stock_location->show_locations('sales');
        $data['comments'] = $this->sale_lib->get_comment();
        $employee_id = $this->Employee->get_logged_in_employee_info()->person_id;
        $employee_info = $this->Employee->get_info($employee_id);
        $data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name[0];
        $data['company_info'] = implode("\n", array(
            $this->config->item('address'),
            $this->config->item('phone'),
            $this->config->item('account_number')
        ));
        $data['invoice_number_enabled'] = $this->sale_lib->is_invoice_mode();
        $data['cur_giftcard_value'] = $this->sale_lib->get_giftcard_remainder();
        $data['cur_rewards_value'] = $this->sale_lib->get_rewards_remainder();
        $data['print_after_sale'] = $this->sale_lib->is_print_after_sale();
        $data['email_receipt'] = $this->sale_lib->get_email_receipt();
        $customer_id = $this->sale_lib->get_customer();
        $data["invoice_number"] = $this->sale_lib->get_invoice_number();
        $data["quote_number"] = $this->sale_lib->get_quote_number();
        $customer_info = $this->_load_customer_data($customer_id, $data);

        $data['taxes'] = $this->sale_lib->get_taxes();
        $data['discount'] = $this->sale_lib->get_discount();
        $data['payments'] = $this->sale_lib->get_payments();

        // Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total'
        $totals = $this->sale_lib->get_totals();
        $data['subtotal'] = $totals['discounted_subtotal'];
        $data['cash_total'] = $totals['cash_total'];
        $data['cash_amount_due'] = $totals['cash_amount_due'];
        $data['non_cash_total'] =$totals['total'];
        $data['non_cash_amount_due'] =$totals['amount_due'];
        $data['payments_total'] = $totals['payment_total'];
        $data['payments_cover_total'] = $totals['payments_cover_total'];
        $data['cash_rounding'] = $this->session->userdata('cash_rounding');

        $data['discounted_subtotal'] = $totals['discounted_subtotal'];
        $data['tax_exclusive_subtotal'] = $totals['tax_exclusive_subtotal'];

        if($data['cash_rounding'])
        {
            $data['total'] = $totals['cash_total'];
            $data['amount_due'] = $totals['cash_amount_due'];
        }
        else
        {
            $data['total'] = $totals['total'];
            $data['amount_due'] = $totals['amount_due'];
        }
        $data['amount_change'] = $data['amount_due'] * -1;

        if($this->sale_lib->is_invoice_mode() || $data['invoice_number_enabled'] == TRUE)
        {
            // generate final invoice number (if using the invoice in sales by receipt mode then the invoice number can be manually entered or altered in some way
            if($this->sale_lib->is_sale_by_receipt_mode())
            {
                $this->sale_lib->set_invoice_number($this->input->post('invoice_number'), $keep_custom = TRUE);
                $invoice_format = $this->sale_lib->get_invoice_number();
                if(empty($invoice_format))
                {
                    $invoice_format = $this->config->item('sales_invoice_format');
                }
            }
            else
            {
                $invoice_format = $this->config->item('sales_invoice_format');
            }
            $invoice_number = $this->token_lib->render($invoice_format);

            // TODO If duplicate invoice then determine the number of employees and repeat until until success or tried the number of employees (if QSEQ was used).
            if($this->Sale->check_invoice_number_exists($invoice_number))
            {
                $data['error'] = $this->lang->line('sales_invoice_number_duplicate');
                $this->_reload($data);
            }
            else
            {
                $data['invoice_number'] = $invoice_number;
                $data['sale_status'] = '0'; // Complete

                // Save the data to the sales table
                $data['sale_id_num'] = $this->Sale->save($data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $data["quote_number"], $data['payments'], $data['dinner_table'], $data['taxes']);
                $data['sale_id'] = 'POS ' . $data['sale_id_num'];

                // Resort and filter cart lines for printing
                $data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']);

                $data = $this->xss_clean($data);

                if($data['sale_id_num'] == -1)
                {
                    $data['error_message'] = $this->lang->line('sales_transaction_failed');
                }
                else
                {
                    $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']);
                    $this->load->view('sales/invoice', $data);
                    $this->sale_lib->clear_all();
                }
            }
        }
        elseif($this->sale_lib->is_quote_mode())
        {
            $invoice_number = NULL;
            $quote_number = $this->sale_lib->get_quote_number();

            if($quote_number == NULL)
            {
                // generate quote number
                $quote_format = $this->config->item('sales_quote_format');
                $quote_number = $this->token_lib->render($quote_format);
            }

            // TODO If duplicate quote then determine the number of employees and repeat until until success or tried the number of employees (if QSEQ was used).
            if($this->Sale->check_quote_number_exists($quote_number))
            {
                $data['error'] = $this->lang->line('sales_quote_number_duplicate');
                $this->_reload($data);
            }
            else
            {
                $data['invoice_number'] = $invoice_number;
                $data['quote_number'] = $quote_number;
                $data['sale_status'] = '1'; // Suspended

                $data['sale_id_num'] = $this->Sale->save($data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $quote_number, $data['payments'], $data['dinner_table'], $data['taxes']);

                $data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']);

                $data = $this->xss_clean($data);


                $data['barcode'] = NULL;

//              $this->suspend_quote($quote_number);

                $this->load->view('sales/quote', $data);
                $this->sale_lib->clear_mode();
                $this->sale_lib->clear_all();
            }
        }
        else
        {
            // Save the data to the sales table
            $data['sale_status'] = '0'; // Complete
            $data['sale_id_num'] = $this->Sale->save($data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], NULL, NULL, $data['payments'], $data['dinner_table'], $data['taxes']);

            $data['sale_id'] = 'POS ' . $data['sale_id_num'];

            $data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']);
            $data = $this->xss_clean($data);

            if($data['sale_id_num'] == -1)
            {
                $data['error_message'] = $this->lang->line('sales_transaction_failed');
            }
            else
            {
                $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']);

                // Reload (sorted) and filter the cart line items for printing purposes
                $data['cart'] = $this->get_filtered($this->sale_lib->get_cart_reordered($data['sale_id_num']));

                $this->load->view('sales/receipt', $data);
                $this->sale_lib->clear_all();
            }
        }
    }

    public function send_invoice($sale_id)
    {
        $sale_data = $this->_load_sale_data($sale_id);

        $result = FALSE;
        $message = $this->lang->line('sales_invoice_no_email');

        if(!empty($sale_data['customer_email']))
        {
            $to = $sale_data['customer_email'];
            $subject = $this->lang->line('sales_invoice') . ' ' . $sale_data['invoice_number'];

            $text = $this->config->item('invoice_email_message');
            $text = str_replace('$INV', $sale_data['invoice_number'], $text);
            $text = str_replace('$CO', 'POS ' . $sale_data['sale_id'], $text);
            $text = $this->_substitute_customer($text, (object)$sale_data);

            // generate email attachment: invoice in pdf format
            $html = $this->load->view('sales/invoice_email', $sale_data, TRUE);
            // load pdf helper
            $this->load->helper(array('dompdf', 'file'));
            $filename = sys_get_temp_dir() . '/' . $this->lang->line('sales_invoice') . '-' . str_replace('/', '-', $sale_data['invoice_number']) . '.pdf';
            if(file_put_contents($filename, pdf_create($html)) !== FALSE)
            {
                $result = $this->email_lib->sendEmail($to, $subject, $text, $filename);
            }

            $message = $this->lang->line($result ? 'sales_invoice_sent' : 'sales_invoice_unsent') . ' ' . $to;
        }

        echo json_encode(array('success' => $result, 'message' => $message, 'id' => $sale_id));

        $this->sale_lib->clear_all();

        return $result;
    }

    public function send_quote($sale_id)
    {
        $sale_data = $this->_load_sale_data($sale_id);

        $result = FALSE;
        $message = $this->lang->line('sales_invoice_no_email');

        if(!empty($sale_data['customer_email']))
        {
            $to = $sale_data['customer_email'];
            $subject = $this->lang->line('sales_quote') . ' ' . $sale_data['quote_number'];

            $text = $this->config->item('invoice_email_message');
            $text = str_replace('$INV', $sale_data['invoice_number'], $text);
            $text = str_replace('$CO', 'POS ' . $sale_data['sale_id'], $text);
            $text = $this->_substitute_customer($text, (object)$sale_data);

            // generate email attachment: invoice in pdf format
            $html = $this->load->view('sales/quote_email', $sale_data, TRUE);
            // load pdf helper
            $this->load->helper(array('dompdf', 'file'));
            $filename = sys_get_temp_dir() . '/' . $this->lang->line('sales_quote') . '-' . str_replace('/', '-', $sale_data['quote_number']) . '.pdf';
            if(file_put_contents($filename, pdf_create($html)) !== FALSE)
            {
                $result = $this->email_lib->sendEmail($to, $subject, $text, $filename);
            }

            $message = $this->lang->line($result ? 'sales_quote_sent' : 'sales_quote_unsent') . ' ' . $to;
        }

        echo json_encode(array('success' => $result, 'message' => $message, 'id' => $sale_id));

        $this->sale_lib->clear_all();

        return $result;
    }

    public function send_receipt($sale_id)
    {
        $sale_data = $this->_load_sale_data($sale_id);

        $result = FALSE;
        $message = $this->lang->line('sales_receipt_no_email');

        if(!empty($sale_data['customer_email']))
        {
            $sale_data['barcode'] = $this->barcode_lib->generate_receipt_barcode($sale_data['sale_id']);

            $to = $sale_data['customer_email'];
            $subject = $this->lang->line('sales_receipt');

            $text = $this->load->view('sales/receipt_email', $sale_data, TRUE);

            $result = $this->email_lib->sendEmail($to, $subject, $text);

            $message = $this->lang->line($result ? 'sales_receipt_sent' : 'sales_receipt_unsent') . ' ' . $to;
        }

        echo json_encode(array('success' => $result, 'message' => $message, 'id' => $sale_id));

        $this->sale_lib->clear_all();

        return $result;
    }

    private function _substitute_variable($text, $variable, $object, $function)
    {
        // don't query if this variable isn't used
        if(strstr($text, $variable))
        {
            $value = call_user_func(array($object, $function));
            $text = str_replace($variable, $value, $text);
        }

        return $text;
    }

    private function _substitute_customer($text, $customer_info)
    {
        // substitute customer info
        $customer_id = $this->sale_lib->get_customer();
        if($customer_id != -1 && $customer_info != '')
        {
            $text = str_replace('$CU', $customer_info->first_name . ' ' . $customer_info->last_name, $text);
            $words = preg_split("/\s+/", trim($customer_info->first_name . ' ' . $customer_info->last_name));
            $acronym = '';

            foreach($words as $w)
            {
                $acronym .= $w[0];
            }
            $text = str_replace('$CI', $acronym, $text);
        }

        return $text;
    }

    private function _is_custom_invoice_number($customer_info)
    {
        $invoice_number = $this->config->config['sales_invoice_format'];
        $invoice_number = $this->_substitute_variables($invoice_number, $customer_info);

        return $this->sale_lib->get_invoice_number() != $invoice_number;
    }

    private function _is_custom_quote_number($customer_info)
    {
        $quote_number = $this->config->config['sales_quote_format'];
        $quote_number = $this->_substitute_variables($quote_number, $customer_info);

        return $this->sale_lib->get_quote_number() != $quote_number;
    }

    private function _substitute_variables($text, $customer_info)
    {
        $text = $this->_substitute_variable($text, '$YCO', $this->Sale, 'get_invoice_number_for_year');
        $text = $this->_substitute_variable($text, '$CO', $this->Sale, 'get_invoice_count');
        $text = $this->_substitute_variable($text, '$SCO', $this->Sale, 'get_suspended_invoice_count');
        $text = strftime($text);
        $text = $this->_substitute_customer($text, $customer_info);

        return $text;
    }

    private function _substitute_invoice_number($customer_info)
    {
        $invoice_number = $this->config->config['sales_invoice_format'];
        $invoice_number = $this->_substitute_variables($invoice_number, $customer_info);
        $this->sale_lib->set_invoice_number($invoice_number, TRUE);

        return $this->sale_lib->get_invoice_number();
    }

    private function _substitute_quote_number($customer_info)
    {
        $quote_number = $this->config->config['sales_quote_format'];
        $quote_number = $this->_substitute_variables($quote_number, $customer_info);
        $this->sale_lib->set_quote_number($quote_number, TRUE);

        return $this->sale_lib->get_quote_number();
    }

    private function _load_customer_data($customer_id, &$data, $stats = FALSE)
    {
        $customer_info = '';

        if($customer_id != -1)
        {
            $customer_info = $this->Customer->get_info($customer_id);
            if(isset($customer_info->company_name))
            {
                $data['customer'] = $customer_info->company_name;
            }
            else
            {
                $data['customer'] = $customer_info->first_name . ' ' . $customer_info->last_name;
            }
            $data['first_name'] = $customer_info->first_name;
            $data['last_name'] = $customer_info->last_name;
            $data['customer_email'] = $customer_info->email;
            $data['customer_address'] = $customer_info->address_1;
            if(!empty($customer_info->zip) || !empty($customer_info->city))
            {
                $data['customer_location'] = $customer_info->zip . ' ' . $customer_info->city;
            }
            else
            {
                $data['customer_location'] = '';
            }
            $data['customer_account_number'] = $customer_info->account_number;
            $data['customer_discount_percent'] = $customer_info->discount_percent;
            $package_id = $this->Customer->get_info($customer_id)->package_id;
            if($package_id != NULL)
            {
                $package_name = $this->Customer_rewards->get_name($package_id);
                $points = $this->Customer->get_info($customer_id)->points;
                $data['customer_rewards']['package_id'] = $package_id;
                $data['customer_rewards']['points'] = ($points==NULL ? 0 : $points);
                $data['customer_rewards']['package_name'] = $package_name;
            }

            if($stats)
            {
                $cust_stats = $this->Customer->get_stats($customer_id);
                $data['customer_total'] = empty($cust_stats) ? 0 : $cust_stats->total;
            }

            $data['customer_info'] = implode("\n", array(
                $data['customer'],
                $data['customer_address'],
                $data['customer_location'],
                $data['customer_account_number']
            ));
        }

        return $customer_info;
    }

    private function _load_sale_data($sale_id)
    {
        $this->sale_lib->clear_all();
        $this->sale_lib->reset_cash_flags();
        $sale_info = $this->Sale->get_info($sale_id)->row_array();
        $this->sale_lib->copy_entire_sale($sale_id);
        $data = array();
        $data['cart'] = $this->sale_lib->get_cart();
        $data['payments'] = $this->sale_lib->get_payments();
        $data['selected_payment_type'] = $this->sale_lib->get_payment_type();

//      $data['subtotal'] = $this->sale_lib->get_subtotal();
//      $data['discounted_subtotal'] = $this->sale_lib->get_subtotal(TRUE);
//      $data['tax_exclusive_subtotal'] = $this->sale_lib->get_subtotal(TRUE, TRUE);
//      $data['amount_due'] = $this->sale_lib->get_amount_due();
//      $data['amount_change'] = $this->sale_lib->get_amount_due() * -1;
//      $data['total'] = $this->sale_lib->get_total();

        $data['taxes'] = $this->sale_lib->get_taxes();
        $data['discount'] = $this->sale_lib->get_discount();
        $data['receipt_title'] = $this->lang->line('sales_receipt');
        $data['transaction_time'] = date($this->config->item('dateformat') . ' ' . $this->config->item('timeformat'), strtotime($sale_info['sale_time']));
        $data['transaction_date'] = date($this->config->item('dateformat'), strtotime($sale_info['sale_time']));
        $data['show_stock_locations'] = $this->Stock_location->show_locations('sales');


        // Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total'
        $totals = $this->sale_lib->get_totals();
        $data['subtotal'] = $totals['subtotal'];
        $data['discounted_subtotal'] = $totals['discounted_subtotal'];
        $data['tax_exclusive_subtotal'] = $totals['tax_exclusive_subtotal'];
        $data['cash_total'] = $totals['cash_total'];
        $data['cash_amount_due'] = $totals['cash_amount_due'];
        $data['non_cash_total'] = $totals['total'];
        $data['non_cash_amount_due'] = $totals['amount_due'];
        $data['payments_total'] = $totals['payment_total'];
        $data['payments_cover_total'] = $totals['payments_cover_total'];

        if($this->session->userdata('cash_rounding'))
        {
            $data['total'] = $totals['cash_total'];
            $data['amount_due'] = $totals['cash_amount_due'];
        }
        else
        {
            $data['total'] = $totals['total'];
            $data['amount_due'] = $totals['amount_due'];
        }
        $data['amount_change'] = $data['amount_due'] * -1;

        $employee_info = $this->Employee->get_info($this->sale_lib->get_employee());
        $data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name[0];
        $this->_load_customer_data($this->sale_lib->get_customer(), $data);

        $data['sale_id_num'] = $sale_id;
        $data['sale_id'] = 'POS ' . $sale_id;
        $data['comments'] = $sale_info['comment'];
        $data['invoice_number'] = $sale_info['invoice_number'];
        $data['quote_number'] = $sale_info['quote_number'];
        $data['sale_status'] = $sale_info['sale_status'];
        $data['company_info'] = implode("\n", array(
            $this->config->item('address'),
            $this->config->item('phone'),
            $this->config->item('account_number')
        ));
        $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']);
        $data['print_after_sale'] = FALSE;
        if($this->sale_lib->get_mode() == 'sale_invoice')
        {
            $data['mode_label'] = $this->lang->line('sales_invoice');
        }
        elseif($this->sale_lib->get_mode() == 'sale_quote')
        {
            $data['mode_label'] = $this->lang->line('sales_quote');
        }

        return $this->xss_clean($data);
    }

    private function _reload($data = array())
    {
        $data['cart'] = $this->sale_lib->get_cart();
        $customer_info = $this->_load_customer_data($this->sale_lib->get_customer(), $data, TRUE);

        if($this->config->item('invoice_enable') == '0')
        {
            $data['modes'] = array(
                'sale' => $this->lang->line('sales_sale'),
                'return' => $this->lang->line('sales_return'));
        }
        else
        {
            $data['modes'] = array(
                'sale' => $this->lang->line('sales_sale'),
                'sale_invoice' => $this->lang->line('sales_sale_by_invoice'),
                'sale_quote' => $this->lang->line('sales_quote'),
                'return' => $this->lang->line('sales_return'));
        }
        $data['mode'] = $this->sale_lib->get_mode();
        $data['empty_tables'] = $this->sale_lib->get_empty_tables();
        $data['selected_table'] = $this->sale_lib->get_dinner_table();
        $data['stock_locations'] = $this->Stock_location->get_allowed_locations('sales');
        $data['stock_location'] = $this->sale_lib->get_sale_location();
        $data['tax_exclusive_subtotal'] = $this->sale_lib->get_subtotal(TRUE, TRUE);

        $data['taxes'] = $this->sale_lib->get_taxes();
        $data['discount'] = $this->sale_lib->get_discount();
        $data['payments'] = $this->sale_lib->get_payments();

        // Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total'
        $totals = $this->sale_lib->get_totals();
        $data['subtotal'] = $totals['discounted_subtotal'];
        $data['cash_total'] = $totals['cash_total'];
        $data['cash_amount_due'] = $totals['cash_amount_due'];
        $data['non_cash_total'] =$totals['total'];
        $data['non_cash_amount_due'] =$totals['amount_due'];
        $data['payments_total'] = $totals['payment_total'];
        $data['payments_cover_total'] = $totals['payments_cover_total'];
        $data['cash_rounding'] = $this->session->userdata('cash_rounding');

        if($data['cash_rounding'])
        {
            $data['total'] = $totals['cash_total'];
            $data['amount_due'] = $totals['cash_amount_due'];
        }
        else
        {
            $data['total'] = $totals['total'];
            $data['amount_due'] = $totals['amount_due'];
        }
        $data['amount_change'] = $data['amount_due'] * -1;

        $data['comment'] = $this->sale_lib->get_comment();
        $data['email_receipt'] = $this->sale_lib->get_email_receipt();
        $data['selected_payment_type'] = $this->sale_lib->get_payment_type();
        if($customer_info && $this->config->item('customer_reward_enable') == TRUE)
        {
            $data['payment_options'] = $this->Sale->get_payment_options(TRUE, TRUE);
        }
        else
        {
            $data['payment_options'] = $this->Sale->get_payment_options();
        }
        $quote_number = $this->sale_lib->get_quote_number();
        if($quote_number != NULL)
        {
            $data['quote_number'] = $quote_number;
        }

        $data['items_module_allowed'] = $this->Employee->has_grant('items', $this->Employee->get_logged_in_employee_info()->person_id);

        $invoice_format = $this->config->item('sales_invoice_format');
        $data['invoice_format'] = $invoice_format;

        $this->set_invoice_number($invoice_format);
        $data['invoice_number'] = $invoice_format;

        $data['invoice_number_enabled'] = $this->sale_lib->is_invoice_mode();
        $data['print_after_sale'] = $this->sale_lib->is_print_after_sale();
        $data['quote_or_invoice_mode'] = $data['mode'] == 'sale_invoice' || $data['mode'] == 'sale_quote';
        $data['sales_or_return_mode'] = $data['mode'] == 'sale' || $data['mode'] == 'return';
        if($this->sale_lib->get_mode() == 'sale_invoice')
        {
            $data['mode_label'] = $this->lang->line('sales_invoice');
        }
        elseif($this->sale_lib->get_mode() == 'sale_quote')
        {
            $data['mode_label'] = $this->lang->line('sales_quote');
        }
        else
        {
            $data['mode_label'] = $this->lang->line('sales_receipt');
        }
        $data = $this->xss_clean($data);

        $this->load->view("sales/register", $data);
    }

    public function receipt($sale_id)
    {
        $data = $this->_load_sale_data($sale_id);
        $this->load->view('sales/receipt', $data);
        $this->sale_lib->clear_all();
    }

    public function invoice($sale_id)
    {
        $data = $this->_load_sale_data($sale_id);
        $this->load->view('sales/invoice', $data);
        $this->sale_lib->clear_all();
    }

    public function edit($sale_id)
    {
        $data = array();

        $data['employees'] = array();
        foreach($this->Employee->get_all()->result() as $employee)
        {
            foreach(get_object_vars($employee) as $property => $value)
            {
                $employee->$property = $this->xss_clean($value);
            }

            $data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
        }

        $sale_info = $this->xss_clean($this->Sale->get_info($sale_id)->row_array());
        $data['selected_customer_name'] = $sale_info['customer_name'];
        $data['selected_customer_id'] = $sale_info['customer_id'];
        $data['sale_info'] = $sale_info;

        $data['payments'] = array();
        foreach($this->Sale->get_sale_payments($sale_id)->result() as $payment)
        {
            foreach(get_object_vars($payment) as $property => $value)
            {
                $payment->$property = $this->xss_clean($value);
            }
            $data['payments'][] = $payment;
        }

        // don't allow gift card to be a payment option in a sale transaction edit because it's a complex change
        $data['payment_options'] = $this->xss_clean($this->Sale->get_payment_options(FALSE));
        $this->load->view('sales/form', $data);
    }

    public function delete($sale_id = -1, $update_inventory = TRUE)
    {
        $employee_id = $this->Employee->get_logged_in_employee_info()->person_id;
        $sale_ids = $sale_id == -1 ? $this->input->post('ids') : array($sale_id);

        if($this->Sale->delete_list($sale_ids, $employee_id, $update_inventory))
        {
            echo json_encode(array('success' => TRUE, 'message' => $this->lang->line('sales_successfully_deleted') . ' ' .
                count($sale_ids) . ' ' . $this->lang->line('sales_one_or_multiple'), 'ids' => $sale_ids));
        }
        else
        {
            echo json_encode(array('success' => FALSE, 'message' => $this->lang->line('sales_unsuccessfully_deleted')));
        }
    }

    public function save($sale_id = -1)
    {
        $newdate = $this->input->post('date');
        $date_formatter = date_create_from_format($this->config->item('dateformat') . ' ' . $this->config->item('timeformat'), $newdate);
        $sale_data = array(
            'sale_time' => $date_formatter->format('Y-m-d H:i:s'),
            'customer_id' => $this->input->post('customer_id') != '' ? $this->input->post('customer_id') : NULL,
            'employee_id' => $this->input->post('employee_id'),
            'comment' => $this->input->post('comment'),
            'invoice_number' => $this->input->post('invoice_number') != '' ? $this->input->post('invoice_number') : NULL
        );

        // go through all the payment type input from the form, make sure the form matches the name and iterator number
        $payments = array();
        $number_of_payments = $this->input->post('number_of_payments');
        for($i = 0; $i < $number_of_payments; ++$i)
        {
            $payment_amount = $this->input->post('payment_amount_' . $i);
            $payment_type = $this->input->post('payment_type_' . $i);
            // remove any 0 payment if by mistake any was introduced at sale time
            if($payment_amount != 0)
            {
                // search for any payment of the same type that was already added, if that's the case add up the new payment amount
                $key = FALSE;
                if(!empty($payments))
                {
                    // search in the multi array the key of the entry containing the current payment_type
                    // NOTE: in PHP5.5 the array_map could be replaced by an array_column
                    $key = array_search($payment_type, array_map(function ($v)
                    {
                        return $v['payment_type'];
                    }, $payments));
                }

                // if no previous payment is found add a new one
                if($key === FALSE)
                {
                    $payments[] = array('payment_type' => $payment_type, 'payment_amount' => $payment_amount);
                }
                else
                {
                    // add up the new payment amount to an existing payment type
                    $payments[$key]['payment_amount'] += $payment_amount;
                }
            }
        }

        if($this->Sale->update($sale_id, $sale_data, $payments))
        {
            echo json_encode(array('success' => TRUE, 'message' => $this->lang->line('sales_successfully_updated'), 'id' => $sale_id));
        }
        else
        {
            echo json_encode(array('success' => FALSE, 'message' => $this->lang->line('sales_unsuccessfully_updated'), 'id' => $sale_id));
        }
    }

    public function cancel()
    {
        $this->sale_lib->clear_all();
        $this->_reload();
    }

    public function discard_quote()
    {
        $suspended_id = $this->sale_lib->get_suspended_id();
        $this->sale_lib->clear_all();
        $this->Sale->delete_suspended_sale($suspended_id);
        $this->_reload();
    }

    public function suspend()
    {
        $dinner_table = $this->sale_lib->get_dinner_table();
        $cart = $this->sale_lib->get_cart();
        $payments = $this->sale_lib->get_payments();
        $employee_id = $this->Employee->get_logged_in_employee_info()->person_id;
        $customer_id = $this->sale_lib->get_customer();
        $customer_info = $this->Customer->get_info($customer_id);
        $invoice_number = $this->_is_custom_invoice_number($customer_info) ? $this->sale_lib->get_invoice_number() : NULL;
        $quote_number = $this->sale_lib->get_quote_number();
        $comment = $this->sale_lib->get_comment();
        $sale_status = '1';

        $data = array();
        $sales_taxes = array();
        if($this->Sale->save($sale_status, $cart, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments, $dinner_table, $sales_taxes) == '-1')
        {
            $data['error'] = $this->lang->line('sales_unsuccessfully_suspended_sale');
        }
        else
        {
            $data['success'] = $this->lang->line('sales_successfully_suspended_sale');
        }

        $this->sale_lib->clear_all();
        $this->_reload($data);
    }

    public function suspend_quote($quote_number)
    {
        $cart = $this->sale_lib->get_cart();
        $payments = $this->sale_lib->get_payments();
        $employee_id = $this->Employee->get_logged_in_employee_info()->person_id;
        $customer_id = $this->sale_lib->get_customer();
        $customer_info = $this->Customer->get_info($customer_id);
        $dinner_table = $this->sale_lib->get_dinner_table();
        $invoice_number = $this->_is_custom_invoice_number($customer_info) ? $this->sale_lib->get_invoice_number() : NULL;
        $comment = $this->sale_lib->get_comment();
        $sale_status = '2'; // Suspend

        $data = array();
        $sales_taxes = array();
        if($this->Sale->save($sale_status, $cart, $customer_id, $employee_id, $comment, $invoice_number, $quote_number, $payments, $dinner_table, $sales_taxes) == '-1')
        {
            $data['error'] = $this->lang->line('sales_unsuccessfully_suspended_sale');
        }
        else
        {
            $data['success'] = $this->lang->line('sales_successfully_suspended_sale');
        }
    }

    public function suspended()
    {
        $customer_id = $this->sale_lib->get_customer();
        $data = array();
        $data['suspended_sales'] = $this->xss_clean($this->Sale->get_all_suspended($customer_id));
        $data['dinner_table_enable'] = $this->config->item('dinner_table_enable');
        $this->load->view('sales/suspended', $data);
    }

    /*
     * We will eventually drop the current set of "suspended" tables since suspended sales
     * are now stored in the sales tables with a sale_status value of suspended.
     */
    public function unsuspend()
    {
        $sale_id = $this->input->post('suspended_sale_id');
        $this->sale_lib->clear_all();

        if($sale_id > 0)
        {
            $this->sale_lib->copy_entire_sale($sale_id);
            $this->Sale->delete_suspended_sale($sale_id);
        }
        else
        {
            // This will unsuspended older suspended sales
            $sale_id = $sale_id * -1;
            $this->sale_lib->copy_entire_suspended_tables_sale($sale_id);
            $this->Sale_suspended->delete($sale_id);
        }

        $this->_reload();
    }

    public function check_invoice_number()
    {
        $sale_id = $this->input->post('sale_id');
        $invoice_number = $this->input->post('invoice_number');
        $exists = !empty($invoice_number) && $this->Sale->check_invoice_number_exists($invoice_number, $sale_id);
        echo !$exists ? 'true' : 'false';
    }

    public function get_filtered($cart)
    {
        $filtered_cart = array();
        foreach($cart as $id => $item)
        {
            if($item['print_option'] == '0') // always include
            {
                $filtered_cart[$id] = $item;
            }
            elseif($item['print_option'] == '1' && $item['price'] != 0)  // include only if the price is not zero
            {
                $filtered_cart[$id] = $item;
            }
            // print_option 2 is never included
        }

        return $filtered_cart;
    }
}

?>
API documentation generated by ApiGen