ultimatepos/app/Http/Controllers/SellPosController.php

3276 lines
143 KiB
PHP

<?php
/* LICENSE: This source file belongs to The Web Fosters. The customer
* is provided a licence to use it.
* Permission is hereby granted, to any person obtaining the licence of this
* software and associated documentation files (the "Software"), to use the
* Software for personal or business purpose ONLY. The Software cannot be
* copied, published, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. THE AUTHOR CAN FIX
* ISSUES ON INTIMATION. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
* THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @author The Web Fosters <thewebfosters@gmail.com>
* @owner The Web Fosters <thewebfosters@gmail.com>
* @copyright 2018 The Web Fosters
* @license As attached in zip file.
*/
namespace App\Http\Controllers;
use App\Account;
use App\Brands;
use App\Business;
use App\BusinessLocation;
use App\Category;
use App\Contact;
use App\CustomerGroup;
use App\InvoiceLayout;
use App\InvoiceScheme;
use App\Media;
use App\Product;
use App\SellingPriceGroup;
use App\TaxRate;
use App\Transaction;
use App\TransactionPayment;
use App\TransactionSellLine;
use App\TypesOfService;
use App\User;
use App\Utils\BusinessUtil;
use App\Utils\CashRegisterUtil;
use App\Utils\ContactUtil;
use App\Utils\ModuleUtil;
use App\Utils\NotificationUtil;
use App\Utils\ProductUtil;
use App\Utils\TransactionUtil;
use App\Variation;
use App\Warranty;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Razorpay\Api\Api;
use Stripe\Charge;
use Stripe\Stripe;
use Yajra\DataTables\Facades\DataTables;
use App\Events\SellCreatedOrModified;
class SellPosController extends Controller
{
/**
* All Utils instance.
*/
protected $contactUtil;
protected $productUtil;
protected $businessUtil;
protected $transactionUtil;
protected $cashRegisterUtil;
protected $moduleUtil;
protected $notificationUtil;
/**
* Constructor
*
* @param ProductUtils $product
* @return void
*/
public function __construct(
ContactUtil $contactUtil,
ProductUtil $productUtil,
BusinessUtil $businessUtil,
TransactionUtil $transactionUtil,
CashRegisterUtil $cashRegisterUtil,
ModuleUtil $moduleUtil,
NotificationUtil $notificationUtil
) {
$this->contactUtil = $contactUtil;
$this->productUtil = $productUtil;
$this->businessUtil = $businessUtil;
$this->transactionUtil = $transactionUtil;
$this->cashRegisterUtil = $cashRegisterUtil;
$this->moduleUtil = $moduleUtil;
$this->notificationUtil = $notificationUtil;
$this->dummyPaymentLine = ['method' => 'cash', 'amount' => 0, 'note' => '', 'card_transaction_number' => '', 'card_number' => '', 'card_type' => '', 'card_holder_name' => '', 'card_month' => '', 'card_year' => '', 'card_security' => '', 'cheque_number' => '', 'bank_account_number' => '',
'is_return' => 0, 'transaction_no' => ''];
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
if (!auth()->user()->can('sell.view') && !auth()->user()->can('sell.create')) {
abort(403, 'Unauthorized action.');
}
$business_id = request()->session()->get('user.business_id');
$business_locations = BusinessLocation::forDropdown($business_id, false);
$customers = Contact::customersDropdown($business_id, false);
$sales_representative = User::forDropdown($business_id, false, false, true);
$is_cmsn_agent_enabled = request()->session()->get('business.sales_cmsn_agnt');
$commission_agents = [];
if (!empty($is_cmsn_agent_enabled)) {
$commission_agents = User::forDropdown($business_id, false, true, true);
}
$is_tables_enabled = $this->transactionUtil->isModuleEnabled('tables');
$is_service_staff_enabled = $this->transactionUtil->isModuleEnabled('service_staff');
//Service staff filter
$service_staffs = null;
if ($is_service_staff_enabled) {
$service_staffs = $this->productUtil->serviceStaffDropdown($business_id);
}
$is_types_service_enabled = $this->moduleUtil->isModuleEnabled('types_of_service');
$shipping_statuses = $this->transactionUtil->shipping_statuses();
return view('sale_pos.index')->with(compact('business_locations', 'customers', 'sales_representative', 'is_cmsn_agent_enabled', 'commission_agents', 'service_staffs', 'is_tables_enabled', 'is_service_staff_enabled', 'is_types_service_enabled', 'shipping_statuses'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$business_id = request()->session()->get('user.business_id');
if (!(auth()->user()->can('superadmin') || auth()->user()->can('sell.create') || ($this->moduleUtil->hasThePermissionInSubscription($business_id, 'repair_module') && auth()->user()->can('repair.create')))) {
abort(403, 'Unauthorized action.');
}
//Check if subscribed or not, then check for users quota
if (!$this->moduleUtil->isSubscribed($business_id)) {
return $this->moduleUtil->expiredResponse(action([\App\Http\Controllers\HomeController::class, 'index']));
} elseif (!$this->moduleUtil->isQuotaAvailable('invoices', $business_id)) {
return $this->moduleUtil->quotaExpiredResponse('invoices', $business_id, action([\App\Http\Controllers\SellPosController::class, 'index']));
}
//like:repair
$sub_type = request()->get('sub_type');
//Check if there is a open register, if no then redirect to Create Register screen.
if ($this->cashRegisterUtil->countOpenedRegister() == 0) {
return redirect()->action([\App\Http\Controllers\CashRegisterController::class, 'create'], ['sub_type' => $sub_type]);
}
$register_details = $this->cashRegisterUtil->getCurrentCashRegister(auth()->user()->id);
$walk_in_customer = $this->contactUtil->getWalkInCustomer($business_id);
$business_details = $this->businessUtil->getDetails($business_id);
$taxes = TaxRate::forBusinessDropdown($business_id, true, true);
$payment_lines[] = $this->dummyPaymentLine;
$default_location = !empty($register_details->location_id) ? BusinessLocation::findOrFail($register_details->location_id) : null;
$business_locations = BusinessLocation::forDropdown($business_id, false, true);
$bl_attributes = $business_locations['attributes'];
$business_locations = $business_locations['locations'];
//set first location as default locaton
if (empty($default_location)) {
foreach ($business_locations as $id => $name) {
$default_location = BusinessLocation::findOrFail($id);
break;
}
}
$payment_types = $this->productUtil->payment_types(null, true, $business_id);
//Shortcuts
$shortcuts = json_decode($business_details->keyboard_shortcuts, true);
$pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true);
$commsn_agnt_setting = $business_details->sales_cmsn_agnt;
$commission_agent = [];
if ($commsn_agnt_setting == 'user') {
$commission_agent = User::forDropdown($business_id, false);
} elseif ($commsn_agnt_setting == 'cmsn_agnt') {
$commission_agent = User::saleCommissionAgentsDropdown($business_id, false);
}
//If brands, category are enabled then send else false.
$categories = (request()->session()->get('business.enable_category') == 1) ? Category::catAndSubCategories($business_id) : false;
$brands = (request()->session()->get('business.enable_brand') == 1) ? Brands::forDropdown($business_id)
->prepend(__('lang_v1.all_brands'), 'all') : false;
$change_return = $this->dummyPaymentLine;
$types = Contact::getContactTypes();
$customer_groups = CustomerGroup::forDropdown($business_id);
//Accounts
$accounts = [];
if ($this->moduleUtil->isModuleEnabled('account')) {
$accounts = Account::forDropdown($business_id, true, false, true);
}
//Selling Price Group Dropdown
$price_groups = SellingPriceGroup::forDropdown($business_id);
$default_price_group_id = !empty($default_location->selling_price_group_id) && array_key_exists($default_location->selling_price_group_id, $price_groups) ? $default_location->selling_price_group_id : null;
//Types of service
$types_of_service = [];
if ($this->moduleUtil->isModuleEnabled('types_of_service')) {
$types_of_service = TypesOfService::forDropdown($business_id);
}
$shipping_statuses = $this->transactionUtil->shipping_statuses();
$default_datetime = $this->businessUtil->format_date('now', true);
$featured_products = !empty($default_location) ? $default_location->getFeaturedProducts() : [];
//pos screen view from module
$pos_module_data = $this->moduleUtil->getModuleData('get_pos_screen_view', ['sub_type' => $sub_type, 'job_sheet_id' => request()->get('job_sheet_id')]);
$invoice_layouts = InvoiceLayout::forDropdown($business_id);
$invoice_schemes = InvoiceScheme::forDropdown($business_id);
$default_invoice_schemes = InvoiceScheme::getDefault($business_id);
$edit_discount = auth()->user()->can('edit_product_discount_from_pos_screen');
$edit_price = auth()->user()->can('edit_product_price_from_pos_screen');
//Added check because $users is of no use if enable_contact_assign if false
$users = config('constants.enable_contact_assign') ? User::forDropdown($business_id, false, false, false, true) : [];
return view('sale_pos.create')
->with(compact(
'edit_discount',
'edit_price',
'business_locations',
'bl_attributes',
'business_details',
'taxes',
'payment_types',
'walk_in_customer',
'payment_lines',
'default_location',
'shortcuts',
'commission_agent',
'categories',
'brands',
'pos_settings',
'change_return',
'types',
'customer_groups',
'accounts',
'price_groups',
'types_of_service',
'default_price_group_id',
'shipping_statuses',
'default_datetime',
'featured_products',
'sub_type',
'pos_module_data',
'invoice_schemes',
'default_invoice_schemes',
'invoice_layouts',
'users',
));
}
/**
* Display the POS screen.
* @return \Illuminate\View\View
*/
public function posDisplay(){
$business_id = request()->session()->get('user.business_id');
$business_details = $this->businessUtil->getDetails($business_id);
$pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true);
return view('sale_pos.display', compact('pos_settings'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
if (!auth()->user()->can('sell.create') && !auth()->user()->can('direct_sell.access') && !auth()->user()->can('so.create')) {
abort(403, 'Unauthorized action.');
}
$is_direct_sale = false;
if (!empty($request->input('is_direct_sale'))) {
$is_direct_sale = true;
}
//Check if there is a open register, if no then redirect to Create Register screen.
if (!$is_direct_sale && $this->cashRegisterUtil->countOpenedRegister() == 0) {
return redirect()->action([\App\Http\Controllers\CashRegisterController::class, 'create']);
}
try {
$input = $request->except('_token');
$input['is_quotation'] = 0;
//status is send as quotation from Add sales screen.
if ($input['status'] == 'quotation') {
$input['status'] = 'draft';
$input['is_quotation'] = 1;
$input['sub_status'] = 'quotation';
} elseif ($input['status'] == 'proforma') {
$input['status'] = 'draft';
$input['sub_status'] = 'proforma';
}
//Add change return
$change_return = $this->dummyPaymentLine;
if (!empty($input['payment']['change_return'])) {
$change_return = $input['payment']['change_return'];
unset($input['payment']['change_return']);
}
//Check Customer credit limit
$is_credit_limit_exeeded = $this->transactionUtil->isCustomerCreditLimitExeeded($input);
if ($is_credit_limit_exeeded !== false) {
$credit_limit_amount = $this->transactionUtil->num_f($is_credit_limit_exeeded, true);
$output = ['success' => 0,
'msg' => __('lang_v1.cutomer_credit_limit_exeeded', ['credit_limit' => $credit_limit_amount]),
];
if (!$is_direct_sale) {
return $output;
} else {
return redirect()
->action([\App\Http\Controllers\SellController::class, 'index'])
->with('status', $output);
}
}
if (!empty($input['products'])) {
$business_id = $request->session()->get('user.business_id');
//Check if subscribed or not, then check for users quota
if (!$this->moduleUtil->isSubscribed($business_id)) {
return $this->moduleUtil->expiredResponse();
} elseif (!$this->moduleUtil->isQuotaAvailable('invoices', $business_id)) {
return $this->moduleUtil->quotaExpiredResponse('invoices', $business_id, action([\App\Http\Controllers\SellPosController::class, 'index']));
}
$user_id = $request->session()->get('user.id');
$discount = ['discount_type' => $input['discount_type'],
'discount_amount' => $input['discount_amount'],
];
$invoice_total = $this->productUtil->calculateInvoiceTotal($input['products'], $input['tax_rate_id'], $discount);
DB::beginTransaction();
if (empty($request->input('transaction_date'))) {
$input['transaction_date'] = \Carbon::now();
} else {
$input['transaction_date'] = $this->productUtil->uf_date($request->input('transaction_date'), true);
}
if ($is_direct_sale) {
$input['is_direct_sale'] = 1;
}
//Set commission agent
$input['commission_agent'] = !empty($request->input('commission_agent')) ? $request->input('commission_agent') : null;
$commsn_agnt_setting = $request->session()->get('business.sales_cmsn_agnt');
if ($commsn_agnt_setting == 'logged_in_user') {
$input['commission_agent'] = $user_id;
}
if (isset($input['exchange_rate']) && $this->transactionUtil->num_uf($input['exchange_rate']) == 0) {
$input['exchange_rate'] = 1;
}
//Customer group details
$contact_id = $request->get('contact_id', null);
$cg = $this->contactUtil->getCustomerGroup($business_id, $contact_id);
$input['customer_group_id'] = (empty($cg) || empty($cg->id)) ? null : $cg->id;
//set selling price group id
$price_group_id = $request->has('price_group') ? $request->input('price_group') : null;
//If default price group for the location exists
$price_group_id = $price_group_id == 0 && $request->has('default_price_group') ? $request->input('default_price_group') : $price_group_id;
$input['is_suspend'] = isset($input['is_suspend']) && 1 == $input['is_suspend'] ? 1 : 0;
if ($input['is_suspend']) {
$input['sale_note'] = !empty($input['additional_notes']) ? $input['additional_notes'] : null;
}
//Generate reference number
if (!empty($input['is_recurring'])) {
//Update reference count
$ref_count = $this->transactionUtil->setAndGetReferenceCount('subscription');
$input['subscription_no'] = $this->transactionUtil->generateReferenceNumber('subscription', $ref_count);
}
if (!empty($request->input('invoice_scheme_id'))) {
$input['invoice_scheme_id'] = $request->input('invoice_scheme_id');
}
//Types of service
if ($this->moduleUtil->isModuleEnabled('types_of_service')) {
$input['types_of_service_id'] = $request->input('types_of_service_id');
$price_group_id = !empty($request->input('types_of_service_price_group')) ? $request->input('types_of_service_price_group') : $price_group_id;
$input['packing_charge'] = !empty($request->input('packing_charge')) ?
$this->transactionUtil->num_uf($request->input('packing_charge')) : 0;
$input['packing_charge_type'] = $request->input('packing_charge_type');
$input['service_custom_field_1'] = !empty($request->input('service_custom_field_1')) ?
$request->input('service_custom_field_1') : null;
$input['service_custom_field_2'] = !empty($request->input('service_custom_field_2')) ?
$request->input('service_custom_field_2') : null;
$input['service_custom_field_3'] = !empty($request->input('service_custom_field_3')) ?
$request->input('service_custom_field_3') : null;
$input['service_custom_field_4'] = !empty($request->input('service_custom_field_4')) ?
$request->input('service_custom_field_4') : null;
$input['service_custom_field_5'] = !empty($request->input('service_custom_field_5')) ?
$request->input('service_custom_field_5') : null;
$input['service_custom_field_6'] = !empty($request->input('service_custom_field_6')) ?
$request->input('service_custom_field_6') : null;
}
if ($request->input('additional_expense_value_1') != '') {
$input['additional_expense_key_1'] = $request->input('additional_expense_key_1');
$input['additional_expense_value_1'] = $request->input('additional_expense_value_1');
}
if ($request->input('additional_expense_value_2') != '') {
$input['additional_expense_key_2'] = $request->input('additional_expense_key_2');
$input['additional_expense_value_2'] = $request->input('additional_expense_value_2');
}
if ($request->input('additional_expense_value_3') != '') {
$input['additional_expense_key_3'] = $request->input('additional_expense_key_3');
$input['additional_expense_value_3'] = $request->input('additional_expense_value_3');
}
if ($request->input('additional_expense_value_4') != '') {
$input['additional_expense_key_4'] = $request->input('additional_expense_key_4');
$input['additional_expense_value_4'] = $request->input('additional_expense_value_4');
}
$input['selling_price_group_id'] = $price_group_id;
if ($this->transactionUtil->isModuleEnabled('tables')) {
$input['res_table_id'] = request()->get('res_table_id');
}
if ($this->transactionUtil->isModuleEnabled('service_staff')) {
$input['res_waiter_id'] = request()->get('res_waiter_id');
}
if ($this->transactionUtil->isModuleEnabled('kitchen')) {
$input['is_kitchen_order'] = request()->get('is_kitchen_order');
}
//upload document
$input['document'] = $this->transactionUtil->uploadFile($request, 'sell_document', 'documents');
$transaction = $this->transactionUtil->createSellTransaction($business_id, $input, $invoice_total, $user_id);
//Upload Shipping documents
Media::uploadMedia($business_id, $transaction, $request, 'shipping_documents', false, 'shipping_document');
$this->transactionUtil->createOrUpdateSellLines($transaction, $input['products'], $input['location_id']);
$change_return['amount'] = $input['change_return'] ?? 0;
$change_return['is_return'] = 1;
$input['payment'][] = $change_return;
$is_credit_sale = isset($input['is_credit_sale']) && $input['is_credit_sale'] == 1 ? true : false;
if (!$transaction->is_suspend && !empty($input['payment']) && !$is_credit_sale) {
$this->transactionUtil->createOrUpdatePaymentLines($transaction, $input['payment']);
}
//Check for final and do some processing.
if ($input['status'] == 'final') {
if (!$is_direct_sale) {
//set service staff timer
foreach ($input['products'] as $product_line) {
if (!empty($product_line['res_service_staff_id'])) {
$product = Product::find($product_line['product_id']);
if (!empty($product->preparation_time_in_minutes)) {
$service_staff = User::find($product_line['res_service_staff_id']);
$base_time = \Carbon::parse($transaction->transaction_date);
//if already assigned set base time as available_at
if (!empty($service_staff->available_at) && \Carbon::parse($service_staff->available_at)->gt(\Carbon::now())) {
$base_time = \Carbon::parse($service_staff->available_at);
}
$total_minutes = $product->preparation_time_in_minutes * $this->transactionUtil->num_uf($product_line['quantity']);
$service_staff->available_at = $base_time->addMinutes($total_minutes);
$service_staff->save();
}
}
}
}
//update product stock
foreach ($input['products'] as $product) {
$decrease_qty = $this->productUtil
->num_uf($product['quantity']);
if (!empty($product['base_unit_multiplier'])) {
$decrease_qty = $decrease_qty * $product['base_unit_multiplier'];
}
if ($product['enable_stock']) {
$this->productUtil->decreaseProductQuantity(
$product['product_id'],
$product['variation_id'],
$input['location_id'],
$decrease_qty
);
}
if ($product['product_type'] == 'combo') {
//Decrease quantity of combo as well.
$this->productUtil
->decreaseProductQuantityCombo(
$product['combo'],
$input['location_id']
);
}
}
//Add payments to Cash Register
if (!$is_direct_sale && !$transaction->is_suspend && !empty($input['payment']) && !$is_credit_sale) {
$this->cashRegisterUtil->addSellPayments($transaction, $input['payment']);
}
//Update payment status
$payment_status = $this->transactionUtil->updatePaymentStatus($transaction->id, $transaction->final_total);
$transaction->payment_status = $payment_status;
if ($request->session()->get('business.enable_rp') == 1) {
$redeemed = !empty($input['rp_redeemed']) ? $input['rp_redeemed'] : 0;
$this->transactionUtil->updateCustomerRewardPoints($contact_id, $transaction->rp_earned, 0, $redeemed);
}
//Allocate the quantity from purchase and add mapping of
//purchase & sell lines in
//transaction_sell_lines_purchase_lines table
$business_details = $this->businessUtil->getDetails($business_id);
$pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true);
$business = ['id' => $business_id,
'accounting_method' => $request->session()->get('business.accounting_method'),
'location_id' => $input['location_id'],
'pos_settings' => $pos_settings,
];
$this->transactionUtil->mapPurchaseSell($business, $transaction->sell_lines, 'purchase');
//Auto send notification
$whatsapp_link = $this->notificationUtil->autoSendNotification($business_id, 'new_sale', $transaction, $transaction->contact);
// zatca instant sync if status final type sell
if($transaction->type == 'sell'){
$this->moduleUtil->getModuleData('after_sales', ['transaction' => $transaction]);
}
}
if (!empty($transaction->sales_order_ids)) {
$this->transactionUtil->updateSalesOrderStatus($transaction->sales_order_ids);
}
$this->moduleUtil->getModuleData('after_sale_saved', ['transaction' => $transaction, 'input' => $input]);
Media::uploadMedia($business_id, $transaction, $request, 'documents');
$this->transactionUtil->activityLog($transaction, 'added');
DB::commit();
SellCreatedOrModified::dispatch($transaction, (int) request()->session()->get('user.id'));
if ($request->input('is_save_and_print') == 1) {
$url = $this->transactionUtil->getInvoiceUrl($transaction->id, $business_id);
return redirect()->to($url . '?print_on_load=true');
}
$msg = trans('sale.pos_sale_added');
$receipt = '';
$invoice_layout_id = $request->input('invoice_layout_id');
$print_invoice = false;
if (!$is_direct_sale) {
if ($input['status'] == 'draft') {
$msg = trans('sale.draft_added');
if ($input['is_quotation'] == 1) {
$msg = trans('lang_v1.quotation_added');
$print_invoice = true;
}
} elseif ($input['status'] == 'final') {
$print_invoice = true;
}
}
if ($transaction->is_suspend == 1 && empty($pos_settings['print_on_suspend'])) {
$print_invoice = false;
}
if (!auth()->user()->can('print_invoice')) {
$print_invoice = false;
}
if ($print_invoice) {
$receipt = $this->receiptContent($business_id, $input['location_id'], $transaction->id, null, false, true, $invoice_layout_id);
}
$output = ['success' => 1, 'msg' => $msg, 'receipt' => $receipt];
if (!empty($whatsapp_link)) {
$output['whatsapp_link'] = $whatsapp_link;
}
} else {
$output = ['success' => 0,
'msg' => trans('messages.something_went_wrong'),
];
}
} catch (\Exception $e) {
DB::rollBack();
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$msg = trans('messages.something_went_wrong');
if (get_class($e) == \App\Exceptions\PurchaseSellMismatch::class) {
$msg = $e->getMessage();
}
if (get_class($e) == \App\Exceptions\AdvanceBalanceNotAvailable::class) {
$msg = $e->getMessage();
}
$output = ['success' => 0,
'msg' => $msg,
];
}
if (!$is_direct_sale) {
return $output;
} else {
if ($input['status'] == 'draft') {
if (isset($input['is_quotation']) && $input['is_quotation'] == 1) {
return redirect()
->action([\App\Http\Controllers\SellController::class, 'getQuotations'])
->with('status', $output);
} else {
return redirect()
->action([\App\Http\Controllers\SellController::class, 'getDrafts'])
->with('status', $output);
}
} elseif ($input['status'] == 'quotation') {
return redirect()
->action([\App\Http\Controllers\SellController::class, 'getQuotations'])
->with('status', $output);
} elseif (isset($input['type']) && $input['type'] == 'sales_order') {
return redirect()
->action([\App\Http\Controllers\SalesOrderController::class, 'index'])
->with('status', $output);
} else {
if (!empty($input['sub_type']) && $input['sub_type'] == 'repair') {
$redirect_url = $input['print_label'] == 1 ? action([\Modules\Repair\Http\Controllers\RepairController::class, 'printLabel'], [$transaction->id]) : action([\Modules\Repair\Http\Controllers\RepairController::class, 'index']);
return redirect($redirect_url)
->with('status', $output);
}
return redirect()
->action([\App\Http\Controllers\SellController::class, 'index'])
->with('status', $output);
}
}
}
/**
* Returns the content for the receipt
*
* @param int $business_id
* @param int $location_id
* @param int $transaction_id
* @param string $printer_type = null
* @return array
*/
private function receiptContent(
$business_id,
$location_id,
$transaction_id,
$printer_type = null,
$is_package_slip = false,
$from_pos_screen = true,
$invoice_layout_id = null,
$is_delivery_note = false
) {
$output = ['is_enabled' => false,
'print_type' => 'browser',
'html_content' => null,
'printer_config' => [],
'data' => [],
];
$business_details = $this->businessUtil->getDetails($business_id);
$location_details = BusinessLocation::find($location_id);
if ($from_pos_screen && $location_details->print_receipt_on_invoice != 1) {
return $output;
}
//Check if printing of invoice is enabled or not.
//If enabled, get print type.
$output['is_enabled'] = true;
$invoice_layout_id = !empty($invoice_layout_id) ? $invoice_layout_id : $location_details->invoice_layout_id;
$invoice_layout = $this->businessUtil->invoiceLayout($business_id, $invoice_layout_id);
//Check if printer setting is provided.
$receipt_printer_type = is_null($printer_type) ? $location_details->receipt_printer_type : $printer_type;
$receipt_details = $this->transactionUtil->getReceiptDetails($transaction_id, $location_id, $invoice_layout, $business_details, $location_details, $receipt_printer_type);
$currency_details = [
'symbol' => $business_details->currency_symbol,
'thousand_separator' => $business_details->thousand_separator,
'decimal_separator' => $business_details->decimal_separator,
];
$receipt_details->currency = $currency_details;
if ($is_package_slip) {
$output['html_content'] = view('sale_pos.receipts.packing_slip', compact('receipt_details'))->render();
return $output;
}
if ($is_delivery_note) {
$output['html_content'] = view('sale_pos.receipts.delivery_note', compact('receipt_details'))->render();
return $output;
}
$output['print_title'] = $receipt_details->invoice_no;
//If print type browser - return the content, printer - return printer config data, and invoice format config
if ($receipt_printer_type == 'printer') {
$output['print_type'] = 'printer';
$output['printer_config'] = $this->businessUtil->printerConfig($business_id, $location_details->printer_id);
$output['data'] = $receipt_details;
} else {
$layout = !empty($receipt_details->design) ? 'sale_pos.receipts.' . $receipt_details->design : 'sale_pos.receipts.classic';
$output['html_content'] = view($layout, compact('receipt_details'))->render();
}
return $output;
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$business_id = request()->session()->get('user.business_id');
if (!(auth()->user()->can('superadmin') || auth()->user()->can('sell.update')
|| auth()->user()->can('edit_pos_payment')
|| ($this->moduleUtil->hasThePermissionInSubscription($business_id, 'repair_module') &&
auth()->user()->can('repair.update')))) {
abort(403, 'Unauthorized action.');
}
if (!$this->moduleUtil->isSubscribed($business_id)) {
return $this->moduleUtil->expiredResponse();
}
//Check if the transaction can be edited or not.
$edit_days = request()->session()->get('business.transaction_edit_days');
if (!$this->transactionUtil->canBeEdited($id, $edit_days)) {
return back()
->with('status', ['success' => 0,
'msg' => __('messages.transaction_edit_not_allowed', ['days' => $edit_days])]);
}
//Check if there is a open register, if no then redirect to Create Register screen.
if ($this->cashRegisterUtil->countOpenedRegister() == 0) {
return redirect()->action([\App\Http\Controllers\CashRegisterController::class, 'create']);
}
//Check if return exist then not allowed
if ($this->transactionUtil->isReturnExist($id)) {
return back()->with('status', ['success' => 0,
'msg' => __('lang_v1.return_exist')]);
}
$walk_in_customer = $this->contactUtil->getWalkInCustomer($business_id);
$business_details = $this->businessUtil->getDetails($business_id);
$taxes = TaxRate::forBusinessDropdown($business_id, true, true);
$transaction = Transaction::where('business_id', $business_id)
->where('type', 'sell')
->with(['price_group', 'types_of_service'])
->findorfail($id);
// If ZATCA module is installed and this transaction is successfully synced, prevent edit
$moduleUtil = new ModuleUtil();
if ($moduleUtil->isModuleInstalled('ZatcaIntegrationKsa')
&& class_exists(\Modules\ZatcaIntegrationKsa\Http\Controllers\ZatcaInvoiceController::class)) {
if (!empty($transaction) && $transaction->zatca_status === 'success') {
return back()->with('status', ['success' => 0,
'msg' => __('lang_v1.invoice_synced_to_zatca_cannot_be_edited')]);
}
}
$location_id = $transaction->location_id;
$business_location = BusinessLocation::find($location_id);
$payment_types = $this->productUtil->payment_types($business_location, true);
$location_printer_type = $business_location->receipt_printer_type;
$sell_details = TransactionSellLine::join(
'products AS p',
'transaction_sell_lines.product_id',
'=',
'p.id'
)
->join(
'variations AS variations',
'transaction_sell_lines.variation_id',
'=',
'variations.id'
)
->join(
'product_variations AS pv',
'variations.product_variation_id',
'=',
'pv.id'
)
->leftjoin('variation_location_details AS vld', function ($join) use ($location_id) {
$join->on('variations.id', '=', 'vld.variation_id')
->where('vld.location_id', '=', $location_id);
})
->leftjoin('units', 'units.id', '=', 'p.unit_id')
->leftjoin('units as u', 'p.secondary_unit_id', '=', 'u.id')
->where('transaction_sell_lines.transaction_id', $id)
->with(['warranties'])
->select(
DB::raw("IF(pv.is_dummy = 0, CONCAT(p.name, ' (', pv.name, ':',variations.name, ')'), p.name) AS product_name"),
'p.id as product_id',
'p.enable_stock',
'p.image as product_image',
'p.name as product_actual_name',
'p.type as product_type',
'pv.name as product_variation_name',
'pv.is_dummy as is_dummy',
'variations.name as variation_name',
'variations.sub_sku',
'p.barcode_type',
'p.enable_sr_no',
'variations.id as variation_id',
'units.short_name as unit',
'units.allow_decimal as unit_allow_decimal',
'u.short_name as second_unit',
'transaction_sell_lines.secondary_unit_quantity',
'transaction_sell_lines.tax_id as tax_id',
'transaction_sell_lines.item_tax as item_tax',
'transaction_sell_lines.unit_price as default_sell_price',
'transaction_sell_lines.unit_price_before_discount as unit_price_before_discount',
'transaction_sell_lines.unit_price_inc_tax as sell_price_inc_tax',
'transaction_sell_lines.id as transaction_sell_lines_id',
'transaction_sell_lines.id',
'transaction_sell_lines.quantity as quantity_ordered',
'transaction_sell_lines.sell_line_note as sell_line_note',
'transaction_sell_lines.parent_sell_line_id',
'transaction_sell_lines.lot_no_line_id',
'transaction_sell_lines.line_discount_type',
'transaction_sell_lines.line_discount_amount',
'transaction_sell_lines.res_service_staff_id',
'units.id as unit_id',
'transaction_sell_lines.sub_unit_id',
//qty_available not added when negative to avoid max quanity getting decreased in edit and showing error in max quantity validation
DB::raw('IF(vld.qty_available > 0, vld.qty_available + transaction_sell_lines.quantity, transaction_sell_lines.quantity) AS qty_available')
)
->get();
if (!empty($sell_details)) {
foreach ($sell_details as $key => $value) {
$variation = Variation::with('media')->findOrFail($value->variation_id);
$sell_details[$key]->media = $variation->media;
//If modifier or combo sell line then unset
if (!empty($sell_details[$key]->parent_sell_line_id)) {
unset($sell_details[$key]);
} else {
if ($transaction->status != 'final') {
$actual_qty_avlbl = $value->qty_available - $value->quantity_ordered;
$sell_details[$key]->qty_available = $actual_qty_avlbl;
$value->qty_available = $actual_qty_avlbl;
}
$sell_details[$key]->formatted_qty_available = $this->productUtil->num_f($value->qty_available, false, null, true);
//Add available lot numbers for dropdown to sell lines
$lot_numbers = [];
if (request()->session()->get('business.enable_lot_number') == 1 || request()->session()->get('business.enable_product_expiry') == 1) {
$lot_number_obj = $this->transactionUtil->getLotNumbersFromVariation($value->variation_id, $business_id, $location_id);
foreach ($lot_number_obj as $lot_number) {
//If lot number is selected added ordered quantity to lot quantity available
if ($value->lot_no_line_id == $lot_number->purchase_line_id) {
$lot_number->qty_available += $value->quantity_ordered;
}
$lot_number->qty_formated = $this->productUtil->num_f($lot_number->qty_available);
$lot_numbers[] = $lot_number;
}
}
$sell_details[$key]->lot_numbers = $lot_numbers;
if (!empty($value->sub_unit_id)) {
$value = $this->productUtil->changeSellLineUnit($business_id, $value);
$sell_details[$key] = $value;
}
$sell_details[$key]->formatted_qty_available = $this->productUtil->num_f($value->qty_available, false, null, true);
if ($this->transactionUtil->isModuleEnabled('modifiers')) {
//Add modifier details to sel line details
$sell_line_modifiers = TransactionSellLine::where('parent_sell_line_id', $sell_details[$key]->transaction_sell_lines_id)
->where('children_type', 'modifier')
->get();
$modifiers_ids = [];
if (count($sell_line_modifiers) > 0) {
$sell_details[$key]->modifiers = $sell_line_modifiers;
foreach ($sell_line_modifiers as $sell_line_modifier) {
$modifiers_ids[] = $sell_line_modifier->variation_id;
}
}
$sell_details[$key]->modifiers_ids = $modifiers_ids;
//add product modifier sets for edit
$this_product = Product::find($sell_details[$key]->product_id);
if (count($this_product->modifier_sets) > 0) {
$sell_details[$key]->product_ms = $this_product->modifier_sets;
}
}
//Get details of combo items
if ($sell_details[$key]->product_type == 'combo') {
$sell_line_combos = TransactionSellLine::where('parent_sell_line_id', $sell_details[$key]->transaction_sell_lines_id)
->where('children_type', 'combo')
->get()
->toArray();
if (!empty($sell_line_combos)) {
$sell_details[$key]->combo_products = $sell_line_combos;
}
//calculate quantity available if combo product
$combo_variations = [];
foreach ($sell_line_combos as $combo_line) {
$combo_variations[] = [
'variation_id' => $combo_line['variation_id'],
'quantity' => $combo_line['quantity'] / $sell_details[$key]->quantity_ordered,
'unit_id' => null,
];
}
$sell_details[$key]->qty_available =
$this->productUtil->calculateComboQuantity($location_id, $combo_variations);
if ($transaction->status == 'final') {
$sell_details[$key]->qty_available = $sell_details[$key]->qty_available + $sell_details[$key]->quantity_ordered;
}
$sell_details[$key]->formatted_qty_available = $this->productUtil->num_f($sell_details[$key]->qty_available, false, null, true);
}
}
}
}
$featured_products = $business_location->getFeaturedProducts();
$payment_lines = $this->transactionUtil->getPaymentDetails($id);
//If no payment lines found then add dummy payment line.
if (empty($payment_lines)) {
$payment_lines[] = $this->dummyPaymentLine;
}
$shortcuts = json_decode($business_details->keyboard_shortcuts, true);
$pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true);
$commsn_agnt_setting = $business_details->sales_cmsn_agnt;
$commission_agent = [];
if ($commsn_agnt_setting == 'user') {
$commission_agent = User::forDropdown($business_id, false);
} elseif ($commsn_agnt_setting == 'cmsn_agnt') {
$commission_agent = User::saleCommissionAgentsDropdown($business_id, false);
}
//If brands, category are enabled then send else false.
$categories = (request()->session()->get('business.enable_category') == 1) ? Category::catAndSubCategories($business_id) : false;
$brands = (request()->session()->get('business.enable_brand') == 1) ? Brands::forDropdown($business_id)
->prepend(__('lang_v1.all_brands'), 'all') : false;
$change_return = $this->dummyPaymentLine;
$types = [];
if (auth()->user()->can('supplier.create')) {
$types['supplier'] = __('report.supplier');
}
if (auth()->user()->can('customer.create')) {
$types['customer'] = __('report.customer');
}
if (auth()->user()->can('supplier.create') && auth()->user()->can('customer.create')) {
$types['both'] = __('lang_v1.both_supplier_customer');
}
$customer_groups = CustomerGroup::forDropdown($business_id);
//Accounts
$accounts = [];
if ($this->moduleUtil->isModuleEnabled('account')) {
$accounts = Account::forDropdown($business_id, true, false, true);
}
$waiters = [];
if ($this->productUtil->isModuleEnabled('service_staff') && !empty($pos_settings['inline_service_staff'])) {
$waiters_enabled = true;
$waiters = $this->productUtil->serviceStaffDropdown($business_id);
}
$redeem_details = [];
if (request()->session()->get('business.enable_rp') == 1) {
$redeem_details = $this->transactionUtil->getRewardRedeemDetails($business_id, $transaction->contact_id);
$redeem_details['points'] += $transaction->rp_redeemed;
$redeem_details['points'] -= $transaction->rp_earned;
}
$edit_discount = auth()->user()->can('edit_product_discount_from_pos_screen');
$edit_price = auth()->user()->can('edit_product_price_from_pos_screen');
$shipping_statuses = $this->transactionUtil->shipping_statuses();
$warranties = $this->__getwarranties();
$sub_type = request()->get('sub_type');
//pos screen view from module
$pos_module_data = $this->moduleUtil->getModuleData('get_pos_screen_view', ['sub_type' => $sub_type]);
$invoice_schemes = [];
$default_invoice_schemes = null;
if ($transaction->status == 'draft') {
$invoice_schemes = InvoiceScheme::forDropdown($business_id);
$default_invoice_schemes = InvoiceScheme::getDefault($business_id);
}
$invoice_layouts = InvoiceLayout::forDropdown($business_id);
$customer_due = $this->transactionUtil->getContactDue($transaction->contact_id, $transaction->business_id);
$customer_due = $customer_due != 0 ? $this->transactionUtil->num_f($customer_due, true) : '';
//Added check because $users is of no use if enable_contact_assign if false
$users = config('constants.enable_contact_assign') ? User::forDropdown($business_id, false, false, false, true) : [];
$only_payment = request()->segment(2) == 'payment';
return view('sale_pos.edit')
->with(compact('business_details', 'taxes', 'payment_types', 'walk_in_customer',
'sell_details', 'transaction', 'payment_lines', 'location_printer_type', 'shortcuts',
'commission_agent', 'categories', 'pos_settings', 'change_return', 'types', 'customer_groups',
'brands', 'accounts', 'waiters', 'redeem_details', 'edit_price', 'edit_discount',
'shipping_statuses', 'warranties', 'sub_type', 'pos_module_data', 'invoice_schemes',
'default_invoice_schemes', 'invoice_layouts', 'featured_products', 'customer_due',
'users', 'only_payment'));
}
/**
* Update the specified resource in storage.
* TODO: Add edit log.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
if (!auth()->user()->can('sell.update') && !auth()->user()->can('direct_sell.access') &&
!auth()->user()->can('so.update') && !auth()->user()->can('edit_pos_payment')) {
abort(403, 'Unauthorized action.');
}
try {
$input = $request->except('_token');
//status is send as quotation from edit sales screen.
$input['is_quotation'] = 0;
if ($input['status'] == 'quotation') {
$input['status'] = 'draft';
$input['is_quotation'] = 1;
$input['sub_status'] = 'quotation';
} elseif ($input['status'] == 'proforma') {
$input['status'] = 'draft';
$input['sub_status'] = 'proforma';
$input['is_quotation'] = 0;
} else {
$input['sub_status'] = null;
$input['is_quotation'] = 0;
}
$is_direct_sale = false;
if (!empty($input['products'])) {
//Get transaction value before updating.
$business_id = (int) request()->session()->get('user.business_id');
$transaction_before = Transaction::findForBusiness((int) $id, $business_id);
if (empty($transaction_before)) {
return back()->with('status', ['success' => 0, 'msg' => __('lang_v1.transaction_not_found')]);
}
$status_before = $transaction_before->status;
$rp_earned_before = $transaction_before->rp_earned;
$rp_redeemed_before = $transaction_before->rp_redeemed;
// If ZATCA module is installed and this transaction is successfully synced, prevent update
$moduleUtil = new ModuleUtil();
if ($moduleUtil->isModuleInstalled('ZatcaIntegrationKsa')
&& class_exists(\Modules\ZatcaIntegrationKsa\Http\Controllers\ZatcaInvoiceController::class)) {
if (!empty($transaction_before) && $transaction_before->zatca_status === 'success') {
return back()->with('status', ['success' => 0,
'msg' => __('lang_v1.invoice_synced_to_zatca_cannot_be_edited')]);
}
}
if ($transaction_before->is_direct_sale == 1) {
$is_direct_sale = true;
}
$sales_order_ids = $transaction_before->sales_order_ids ?? [];
//Add change return
$change_return = $this->dummyPaymentLine;
if (!empty($input['payment']['change_return'])) {
$change_return = $input['payment']['change_return'];
unset($input['payment']['change_return']);
}
//Check Customer credit limit
$is_credit_limit_exeeded = $transaction_before->type == 'sell' ? $this->transactionUtil->isCustomerCreditLimitExeeded($input, $id) : false;
if ($is_credit_limit_exeeded !== false) {
$credit_limit_amount = $this->transactionUtil->num_f($is_credit_limit_exeeded, true);
$output = ['success' => 0,
'msg' => __('lang_v1.cutomer_credit_limit_exeeded', ['credit_limit' => $credit_limit_amount]),
];
if (!$is_direct_sale) {
return $output;
} else {
return redirect()
->action([\App\Http\Controllers\SellController::class, 'index'])
->with('status', $output);
}
}
//Check if there is a open register, if no then redirect to Create Register screen.
if (!$is_direct_sale && $this->cashRegisterUtil->countOpenedRegister() == 0) {
return redirect()->action([\App\Http\Controllers\CashRegisterController::class, 'create']);
}
$business_id = $request->session()->get('user.business_id');
$user_id = $request->session()->get('user.id');
$commsn_agnt_setting = $request->session()->get('business.sales_cmsn_agnt');
$discount = ['discount_type' => $input['discount_type'],
'discount_amount' => $input['discount_amount'],
];
$invoice_total = $this->productUtil->calculateInvoiceTotal($input['products'], $input['tax_rate_id'], $discount);
if (!empty($request->input('transaction_date'))) {
$input['transaction_date'] = $this->productUtil->uf_date($request->input('transaction_date'), true);
}
$input['commission_agent'] = !empty($request->input('commission_agent')) ? $request->input('commission_agent') : null;
if ($commsn_agnt_setting == 'logged_in_user') {
$input['commission_agent'] = $user_id;
}
if (isset($input['exchange_rate']) && $this->transactionUtil->num_uf($input['exchange_rate']) == 0) {
$input['exchange_rate'] = 1;
}
//Customer group details
$contact_id = $request->get('contact_id', null);
$cg = $this->contactUtil->getCustomerGroup($business_id, $contact_id);
$input['customer_group_id'] = (empty($cg) || empty($cg->id)) ? null : $cg->id;
//set selling price group id
$price_group_id = $request->has('price_group') ? $request->input('price_group') : null;
$input['is_suspend'] = isset($input['is_suspend']) && 1 == $input['is_suspend'] ? 1 : 0;
if ($input['is_suspend']) {
$input['sale_note'] = !empty($input['additional_notes']) ? $input['additional_notes'] : null;
}
if ($status_before == 'draft' && !empty($request->input('invoice_scheme_id'))) {
$input['invoice_scheme_id'] = $request->input('invoice_scheme_id');
}
//Types of service
if ($this->moduleUtil->isModuleEnabled('types_of_service')) {
$input['types_of_service_id'] = $request->input('types_of_service_id');
$price_group_id = !empty($request->input('types_of_service_price_group')) ? $request->input('types_of_service_price_group') : $price_group_id;
$input['packing_charge'] = !empty($request->input('packing_charge')) ?
$this->transactionUtil->num_uf($request->input('packing_charge')) : 0;
$input['packing_charge_type'] = $request->input('packing_charge_type');
$input['service_custom_field_1'] = !empty($request->input('service_custom_field_1')) ?
$request->input('service_custom_field_1') : null;
$input['service_custom_field_2'] = !empty($request->input('service_custom_field_2')) ?
$request->input('service_custom_field_2') : null;
$input['service_custom_field_3'] = !empty($request->input('service_custom_field_3')) ?
$request->input('service_custom_field_3') : null;
$input['service_custom_field_4'] = !empty($request->input('service_custom_field_4')) ?
$request->input('service_custom_field_4') : null;
$input['service_custom_field_5'] = !empty($request->input('service_custom_field_5')) ?
$request->input('service_custom_field_5') : null;
$input['service_custom_field_6'] = !empty($request->input('service_custom_field_6')) ?
$request->input('service_custom_field_6') : null;
}
$input['selling_price_group_id'] = $price_group_id;
if ($this->transactionUtil->isModuleEnabled('tables')) {
$input['res_table_id'] = request()->get('res_table_id');
}
if ($this->transactionUtil->isModuleEnabled('service_staff')) {
$input['res_waiter_id'] = request()->get('res_waiter_id');
}
if ($this->transactionUtil->isModuleEnabled('kitchen')) {
$input['is_kitchen_order'] = request()->get('is_kitchen_order');
}
//upload document
$document_name = $this->transactionUtil->uploadFile($request, 'sell_document', 'documents');
if (!empty($document_name)) {
$input['document'] = $document_name;
}
if ($request->input('additional_expense_value_1') != '') {
$input['additional_expense_key_1'] = $request->input('additional_expense_key_1');
$input['additional_expense_value_1'] = $request->input('additional_expense_value_1');
}
if ($request->input('additional_expense_value_2') != '') {
$input['additional_expense_key_2'] = $request->input('additional_expense_key_2');
$input['additional_expense_value_2'] = $request->input('additional_expense_value_2');
}
if ($request->input('additional_expense_value_3') != '') {
$input['additional_expense_key_3'] = $request->input('additional_expense_key_3');
$input['additional_expense_value_3'] = $request->input('additional_expense_value_3');
}
if ($request->input('additional_expense_value_4') != '') {
$input['additional_expense_key_4'] = $request->input('additional_expense_key_4');
$input['additional_expense_value_4'] = $request->input('additional_expense_value_4');
}
$only_payment = !$is_direct_sale && !auth()->user()->can('sell.update') && auth()->user()->can('edit_pos_payment');
//if edit pos not allowed and only edit payment allowed
if ($only_payment) {
DB::beginTransaction();
$this->onlyUpdatePayment($transaction_before, $input);
DB::commit();
$can_print_invoice = auth()->user()->can('print_invoice');
$invoice_layout_id = $request->input('invoice_layout_id');
$receipt = $this->receiptContent($business_id, $input['location_id'], $transaction_before->id, null, false, true, $invoice_layout_id);
$msg = trans('purchase.payment_updated_success');
$output = ['success' => 1, 'msg' => $msg, 'receipt' => $receipt];
return $output;
}
//Begin transaction
DB::beginTransaction();
$transaction = $this->transactionUtil->updateSellTransaction($id, $business_id, $input, $invoice_total, $user_id);
//update service staff timer
if (!$is_direct_sale && $transaction->status == 'final') {
foreach ($input['products'] as $product_line) {
if (!empty($product_line['res_service_staff_id'])) {
$product = Product::find($product_line['product_id']);
if (!empty($product->preparation_time_in_minutes)) {
//if quantity not increase skip line
$quantity = $this->transactionUtil->num_uf($product_line['quantity']);
if (!empty($product_line['transaction_sell_lines_id'])) {
$sl = TransactionSellLine::find($product_line['transaction_sell_lines_id']);
if ($sl->quantity >= $quantity && $sl->res_service_staff_id == $product_line['res_service_staff_id']) {
continue;
}
//if same service staff assigned quantity is only increased quantity
if ($sl->res_service_staff_id == $product_line['res_service_staff_id']) {
$quantity = $quantity - $sl->quantity;
}
}
$service_staff = User::find($product_line['res_service_staff_id']);
$base_time = \Carbon::parse($transaction->transaction_date);
//is transaction date is past take base time as now
if ($base_time->lt(\Carbon::now())) {
$base_time = \Carbon::now();
}
//if already assigned set base time as available_at
if (!empty($service_staff->available_at) && \Carbon::parse($service_staff->available_at)->gt(\Carbon::now())) {
$base_time = \Carbon::parse($service_staff->available_at);
}
$total_minutes = $product->preparation_time_in_minutes * $quantity;
$service_staff->available_at = $base_time->addMinutes($total_minutes);
$service_staff->save();
}
}
}
}
//Update Sell lines
$deleted_lines = $this->transactionUtil->createOrUpdateSellLines($transaction, $input['products'], $input['location_id'], true, $status_before);
//Update update lines
$is_credit_sale = isset($input['is_credit_sale']) && $input['is_credit_sale'] == 1 ? true : false;
$new_sales_order_ids = $transaction->sales_order_ids ?? [];
$sales_order_ids = array_unique(array_merge($sales_order_ids, $new_sales_order_ids));
if (!empty($sales_order_ids)) {
$this->transactionUtil->updateSalesOrderStatus($sales_order_ids);
}
if (!$transaction->is_suspend && !$is_credit_sale) {
//Add change return
$change_return['amount'] = $input['change_return'] ?? 0;
$change_return['is_return'] = 1;
if (!empty($input['change_return_id'])) {
$change_return['payment_id'] = $input['change_return_id'];
}
$input['payment'][] = $change_return;
if (!$is_direct_sale || auth()->user()->can('sell.payments')) {
$this->transactionUtil->createOrUpdatePaymentLines($transaction, $input['payment']);
//Update cash register
if (!$is_direct_sale) {
$this->cashRegisterUtil->updateSellPayments($status_before, $transaction, $input['payment']);
}
}
}
if ($request->session()->get('business.enable_rp') == 1) {
$this->transactionUtil->updateCustomerRewardPoints($contact_id, $transaction->rp_earned, $rp_earned_before, $transaction->rp_redeemed, $rp_redeemed_before);
}
Media::uploadMedia($business_id, $transaction, $request, 'shipping_documents', false, 'shipping_document');
if ($transaction->type == 'sell') {
//Update payment status
$payment_status = $this->transactionUtil->updatePaymentStatus($transaction->id, $transaction->final_total);
$transaction->payment_status = $payment_status;
//Update product stock
$this->productUtil->adjustProductStockForInvoice($status_before, $transaction, $input);
//Allocate the quantity from purchase and add mapping of
//purchase & sell lines in
//transaction_sell_lines_purchase_lines table
$business_details = $this->businessUtil->getDetails($business_id);
$pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true);
$business = ['id' => $business_id,
'accounting_method' => $request->session()->get('business.accounting_method'),
'location_id' => $input['location_id'],
'pos_settings' => $pos_settings,
];
$this->transactionUtil->adjustMappingPurchaseSell($status_before, $transaction, $business, $deleted_lines);
//Auto send notification
$whatsapp_link = $this->notificationUtil->autoSendNotification($business_id, 'new_sale', $transaction, $transaction->contact);
// zatca instant sync if status final type sell
if($input['status'] == 'final'){
$this->moduleUtil->getModuleData('after_sales', ['transaction' => $transaction]);
}
}
$log_properties = [];
if (isset($input['repair_completed_on'])) {
$completed_on = !empty($input['repair_completed_on']) ? $this->transactionUtil->uf_date($input['repair_completed_on'], true) : null;
if ($transaction->repair_completed_on != $completed_on) {
$log_properties['completed_on_from'] = $transaction->repair_completed_on;
$log_properties['completed_on_to'] = $completed_on;
}
}
$this->moduleUtil->getModuleData('after_sale_saved', ['transaction' => $transaction, 'input' => $input]);
Media::uploadMedia($business_id, $transaction, $request, 'documents');
$this->transactionUtil->activityLog($transaction, 'edited', $transaction_before);
SellCreatedOrModified::dispatch($transaction, (int) request()->session()->get('user.id'));
DB::commit();
if ($request->input('is_save_and_print') == 1) {
$url = $this->transactionUtil->getInvoiceUrl($id, $business_id);
return redirect()->to($url . '?print_on_load=true');
}
$msg = __('lang_v1.updated_success');
$receipt = '';
$can_print_invoice = auth()->user()->can('print_invoice');
$invoice_layout_id = $request->input('invoice_layout_id');
if ($input['status'] == 'draft' && $input['is_quotation'] == 0) {
$msg = trans('sale.draft_added');
} elseif ($input['status'] == 'draft' && $input['is_quotation'] == 1) {
$msg = trans('lang_v1.quotation_updated');
if (!$is_direct_sale && $can_print_invoice) {
$receipt = $this->receiptContent($business_id, $input['location_id'], $transaction->id, null, false, true, $invoice_layout_id);
} else {
$receipt = '';
}
} elseif ($input['status'] == 'final') {
$msg = trans('sale.pos_sale_updated');
if (!$is_direct_sale && $can_print_invoice) {
$receipt = $this->receiptContent($business_id, $input['location_id'], $transaction->id, null, false, true, $invoice_layout_id);
} else {
$receipt = '';
}
}
$output = ['success' => 1, 'msg' => $msg, 'receipt' => $receipt];
if (!empty($whatsapp_link)) {
$output['whatsapp_link'] = $whatsapp_link;
}
} else {
$output = ['success' => 0,
'msg' => trans('messages.something_went_wrong'),
];
}
} catch (\Exception $e) {
DB::rollBack();
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output = ['success' => 0,
'msg' => __('messages.something_went_wrong'),
];
}
if (!$is_direct_sale) {
return $output;
} else {
if ($input['status'] == 'draft') {
if (isset($input['is_quotation']) && $input['is_quotation'] == 1) {
return redirect()
->action([\App\Http\Controllers\SellController::class, 'getQuotations'])
->with('status', $output);
} else {
return redirect()
->action([\App\Http\Controllers\SellController::class, 'getDrafts'])
->with('status', $output);
}
} else {
if (!empty($transaction->sub_type) && $transaction->sub_type == 'repair') {
return redirect()
->action([\Modules\Repair\Http\Controllers\RepairController::class, 'index'])
->with('status', $output);
}
if ($transaction->type == 'sales_order') {
return redirect()
->action([\App\Http\Controllers\SalesOrderController::class, 'index'])
->with('status', $output);
}
return redirect()
->action([\App\Http\Controllers\SellController::class, 'index'])
->with('status', $output);
}
}
}
/**
* Function to add/edit payments for a pos sale
*/
private function onlyUpdatePayment($transaction, $input)
{
//Add change return
$change_return = $this->dummyPaymentLine;
if (!empty($input['payment']['change_return'])) {
$change_return = $input['payment']['change_return'];
unset($input['payment']['change_return']);
}
//Add change return
$change_return['amount'] = $input['change_return'] ?? 0;
$change_return['is_return'] = 1;
if (!empty($input['change_return_id'])) {
$change_return['payment_id'] = $input['change_return_id'];
}
$input['payment'][] = $change_return;
$this->transactionUtil->createOrUpdatePaymentLines($transaction, $input['payment']);
$this->cashRegisterUtil->updateSellPayments($transaction->status, $transaction, $input['payment']);
//Update payment status
$payment_status = $this->transactionUtil->updatePaymentStatus($transaction->id, $transaction->final_total);
$transaction_before = $transaction;
$transaction->payment_status = $payment_status;
if ($payment_status == 'paid') {
$transaction->is_suspend = 0;
$transaction->save();
}
$this->transactionUtil->activityLog($transaction, 'payment_edited', $transaction_before);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
if (!auth()->user()->can('sell.delete') && !auth()->user()->can('direct_sell.delete') && !auth()->user()->can('so.delete')) {
abort(403, 'Unauthorized action.');
}
if (request()->ajax()) {
try {
$business_id = request()->session()->get('user.business_id');
//Begin transaction
DB::beginTransaction();
$output = $this->transactionUtil->deleteSale($business_id, $id);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output['success'] = false;
$output['msg'] = trans('messages.something_went_wrong');
}
return $output;
}
}
public function getSalesOrderLines()
{
$business_id = request()->session()->get('user.business_id');
$sales_order_id = request()->input('sales_order_id');
$row_count = request()->get('product_row');
$row_count = $row_count + 1;
$sales_order = Transaction::where('business_id', $business_id)
->where('type', 'sales_order')
->with(['sell_lines'])
->find($sales_order_id);
$html = '<table>';
if (!empty($sales_order)) {
foreach ($sales_order->sell_lines as $sell_line) {
$quantity = $sell_line->quantity - $sell_line->so_quantity_invoiced;
$sell_line->qty_available = $quantity;
$sell_line->formatted_qty_available = $this->transactionUtil->num_f($quantity);
$sell_line_row = $this->getSellLineRow($sell_line->variation_id, $sales_order->location_id, $quantity, $row_count, true, false, $sell_line);
$html .= $sell_line_row['html_content'];
$row_count++;
}
}
$html .= '</table>';
return [
'html' => $html,
'sales_order' => $sales_order,
];
}
// is_serial_no to display serial number in sale screen
private function getSellLineRow($variation_id, $location_id, $quantity, $row_count, $is_direct_sell, $is_serial_no, $so_line = null)
{
$business_id = request()->session()->get('user.business_id');
$business_details = $this->businessUtil->getDetails($business_id);
//Check for weighing scale barcode
$weighing_barcode = request()->get('weighing_scale_barcode');
$pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true);
$check_qty = !empty($pos_settings['allow_overselling']) ? false : true;
$is_sales_order = request()->has('is_sales_order') && request()->input('is_sales_order') == 'true' ? true : false;
$is_draft = request()->has('is_draft') && request()->input('is_draft') == 'true' ? true : false;
if ($is_sales_order || !empty($so_line) || $is_draft) {
$check_qty = false;
}
if (request()->input('disable_qty_alert') === 'true') {
$pos_settings['allow_overselling'] = true;
}
$product = $this->productUtil->getDetailsFromVariation($variation_id, $business_id, $location_id, $check_qty);
if (!isset($product->quantity_ordered)) {
$product->quantity_ordered = $quantity;
}
$product->secondary_unit_quantity = !isset($product->secondary_unit_quantity) ? 0 : $product->secondary_unit_quantity;
$product->formatted_qty_available = $this->productUtil->num_f($product->qty_available, false, null, true);
$sub_units = $this->productUtil->getSubUnits($business_id, $product->unit_id, false, $product->product_id);
//Get customer group and change the price accordingly
$customer_id = request()->get('customer_id', null);
$cg = $this->contactUtil->getCustomerGroup($business_id, $customer_id);
$percent = (empty($cg) || empty($cg->amount) || $cg->price_calculation_type != 'percentage') ? 0 : $cg->amount;
$product->default_sell_price = $product->default_sell_price + ($percent * $product->default_sell_price / 100);
$product->sell_price_inc_tax = $product->sell_price_inc_tax + ($percent * $product->sell_price_inc_tax / 100);
$tax_dropdown = TaxRate::forBusinessDropdown($business_id, true, true);
$enabled_modules = $this->transactionUtil->allModulesEnabled();
//Get lot number dropdown if enabled
$lot_numbers = [];
if (request()->session()->get('business.enable_lot_number') == 1 || request()->session()->get('business.enable_product_expiry') == 1) {
$lot_number_obj = $this->transactionUtil->getLotNumbersFromVariation($variation_id, $business_id, $location_id, true);
foreach ($lot_number_obj as $lot_number) {
$lot_number->qty_formated = $this->productUtil->num_f($lot_number->qty_available);
$lot_numbers[] = $lot_number;
}
}
$product->lot_numbers = $lot_numbers;
$purchase_line_id = request()->get('purchase_line_id');
$price_group = request()->input('price_group');
if (!empty($price_group)) {
$variation_group_prices = $this->productUtil->getVariationGroupPrice($variation_id, $price_group, $product->tax_id);
if (!empty($variation_group_prices['price_inc_tax'])) {
$product->sell_price_inc_tax = $variation_group_prices['price_inc_tax'];
$product->default_sell_price = $variation_group_prices['price_exc_tax'];
}
}
$warranties = $this->__getwarranties();
$output['success'] = true;
$output['enable_sr_no'] = $product->enable_sr_no;
$waiters = [];
if ($this->productUtil->isModuleEnabled('service_staff') && !empty($pos_settings['inline_service_staff'])) {
$waiters_enabled = true;
$waiters = $this->productUtil->serviceStaffDropdown($business_id, $location_id);
}
$last_sell_line = null;
if ($is_direct_sell) {
$last_sell_line = $this->getLastSellLineForCustomer($variation_id, $customer_id, $location_id);
}
if (request()->get('type') == 'sell-return') {
$output['html_content'] = view('sell_return.partials.product_row')
->with(compact('product', 'row_count', 'tax_dropdown', 'enabled_modules', 'sub_units'))
->render();
} else {
$is_cg = !empty($cg->id) ? true : false;
$discount = $this->productUtil->getProductDiscount($product, $business_id, $location_id, $is_cg, $price_group, $variation_id);
if ($is_direct_sell) {
$edit_discount = auth()->user()->can('edit_product_discount_from_sale_screen');
$edit_price = auth()->user()->can('edit_product_price_from_sale_screen');
} else {
$edit_discount = auth()->user()->can('edit_product_discount_from_pos_screen');
$edit_price = auth()->user()->can('edit_product_price_from_pos_screen');
}
$output['html_content'] = view('sale_pos.product_row')
->with(compact('product', 'row_count', 'tax_dropdown', 'enabled_modules', 'pos_settings', 'sub_units', 'discount', 'waiters', 'edit_discount', 'edit_price', 'purchase_line_id', 'warranties', 'quantity', 'is_direct_sell', 'so_line', 'is_sales_order', 'last_sell_line', 'is_serial_no'))
->render();
}
return $output;
}
/**
* Finds last sell line of a variation for the customer for a location
*/
private function getLastSellLineForCustomer($variation_id, $customer_id, $location_id)
{
$sell_line = TransactionSellLine::join('transactions as t', 't.id', '=', 'transaction_sell_lines.transaction_id')
->where('t.location_id', $location_id)
->where('t.contact_id', $customer_id)
->where('t.type', 'sell')
->where('t.status', 'final')
->where('transaction_sell_lines.variation_id', $variation_id)
->orderBy('t.transaction_date', 'desc')
->select('transaction_sell_lines.*')
->first();
return $sell_line;
}
/**
* Returns the HTML row for a product in POS
*
* @param int $variation_id
* @param int $location_id
* @return \Illuminate\Http\Response
*/
public function getProductRow($variation_id, $location_id)
{
$output = [];
try {
$row_count = request()->get('product_row');
$row_count = $row_count + 1;
$quantity = request()->get('quantity', 1);
$weighing_barcode = request()->get('weighing_scale_barcode', null);
$is_direct_sell = false;
if (request()->get('is_direct_sell') == 'true') {
$is_direct_sell = true;
}
$is_serial_no = false;
if (request()->get('is_serial_no') == 'true') {
$is_serial_no = true;
}
if ($variation_id == 'null' && !empty($weighing_barcode)) {
$product_details = $this->__parseWeighingBarcode($weighing_barcode);
if ($product_details['success']) {
$variation_id = $product_details['variation_id'];
$quantity = $product_details['qty'];
} else {
$output['success'] = false;
$output['msg'] = $product_details['msg'];
return $output;
}
}
$output = $this->getSellLineRow($variation_id, $location_id, $quantity, $row_count, $is_direct_sell, $is_serial_no);
if ($this->transactionUtil->isModuleEnabled('modifiers') && !$is_direct_sell) {
$variation = Variation::find($variation_id);
$business_id = request()->session()->get('user.business_id');
$this_product = Product::where('business_id', $business_id)
->with(['modifier_sets'])
->find($variation->product_id);
if (count($this_product->modifier_sets) > 0) {
$product_ms = $this_product->modifier_sets;
$output['html_modifier'] = view('restaurant.product_modifier_set.modifier_for_product')
->with(compact('product_ms', 'row_count'))->render();
}
}
} catch (\Exception $e) {
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output['success'] = false;
$output['msg'] = __('lang_v1.item_out_of_stock');
}
return $output;
}
/**
* Returns the HTML row for a payment in POS
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function getPaymentRow(Request $request)
{
$business_id = request()->session()->get('user.business_id');
$row_index = $request->input('row_index');
$location_id = $request->input('location_id');
$removable = true;
$payment_types = $this->productUtil->payment_types($location_id, true);
$payment_line = $this->dummyPaymentLine;
//Accounts
$accounts = [];
if ($this->moduleUtil->isModuleEnabled('account')) {
$accounts = Account::forDropdown($business_id, true, false, true);
}
return view('sale_pos.partials.payment_row')
->with(compact('payment_types', 'row_index', 'removable', 'payment_line', 'accounts'));
}
/**
* Returns recent transactions
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function getRecentTransactions(Request $request)
{
$business_id = $request->session()->get('user.business_id');
$user_id = $request->session()->get('user.id');
$transaction_status = $request->get('status');
$register = $this->cashRegisterUtil->getCurrentCashRegister($user_id);
$query = Transaction::where('business_id', $business_id)
->where('transactions.created_by', $user_id)
->where('transactions.type', 'sell')
->where('is_direct_sale', 0);
if ($transaction_status == 'final') {
//Commented as credit sales not showing
// if (!empty($register->id)) {
// $query->leftjoin('cash_register_transactions as crt', 'transactions.id', '=', 'crt.transaction_id')
// ->where('crt.cash_register_id', $register->id);
// }
}
if ($transaction_status == 'quotation') {
$query->where('transactions.status', 'draft')
->where('sub_status', 'quotation');
} elseif ($transaction_status == 'draft') {
$query->where('transactions.status', 'draft')
->whereNull('sub_status');
} else {
$query->where('transactions.status', $transaction_status);
}
$transaction_sub_type = $request->get('transaction_sub_type');
if (!empty($transaction_sub_type)) {
$query->where('transactions.sub_type', $transaction_sub_type);
} else {
$query->where('transactions.sub_type', null);
}
// Retrieve the limit for displaying recent transactions from the configuration
$limit = config('constants.pos_recent_transactions_display_limit', 10);
$transactions = $query->orderBy('transactions.created_at', 'desc')
->groupBy('transactions.id')
->select('transactions.*')
->with(['contact', 'table'])
->limit($limit)
->get();
return view('sale_pos.partials.recent_transactions')
->with(compact('transactions', 'transaction_sub_type'));
}
/**
* Prints invoice for sell
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function printInvoice(Request $request, $transaction_id)
{
if (request()->ajax()) {
try {
$output = ['success' => 0,
'msg' => trans('messages.something_went_wrong'),
];
$business_id = $request->session()->get('user.business_id');
$transaction = Transaction::where('business_id', $business_id)
->where('id', $transaction_id)
->with(['location'])
->first();
if (empty($transaction)) {
return $output;
}
$printer_type = 'browser';
if (!empty(request()->input('check_location')) && request()->input('check_location') == true) {
$printer_type = $transaction->location->receipt_printer_type;
}
$is_package_slip = !empty($request->input('package_slip')) ? true : false;
$is_delivery_note = !empty($request->input('delivery_note')) ? true : false;
$invoice_layout_id = $transaction->is_direct_sale ? $transaction->location->sale_invoice_layout_id : null;
$receipt = $this->receiptContent($business_id, $transaction->location_id, $transaction_id, $printer_type, $is_package_slip, false, $invoice_layout_id, $is_delivery_note);
if (!empty($receipt)) {
$output = ['success' => 1, 'receipt' => $receipt];
}
} catch (\Exception $e) {
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output = ['success' => 0,
'msg' => trans('messages.something_went_wrong'),
];
}
return $output;
}
}
/**
* Gives suggetion for product based on category
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function getProductSuggestion(Request $request)
{
if ($request->ajax()) {
$category_id = $request->get('category_id');
$brand_id = $request->get('brand_id');
$location_id = $request->get('location_id');
$term = $request->get('term');
$check_qty = false;
$business_id = $request->session()->get('user.business_id');
$business = $request->session()->get('business');
$pos_settings = empty($business->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business->pos_settings, true);
$products = Variation::join('products as p', 'variations.product_id', '=', 'p.id')
->join('product_locations as pl', 'pl.product_id', '=', 'p.id')
->join('units as u', 'p.unit_id', '=', 'u.id')
->leftjoin(
'variation_location_details AS VLD',
function ($join) use ($location_id) {
$join->on('variations.id', '=', 'VLD.variation_id');
//Include Location
if (!empty($location_id)) {
$join->where(function ($query) use ($location_id) {
$query->where('VLD.location_id', '=', $location_id);
//Check null to show products even if no quantity is available in a location.
//TODO: Maybe add a settings to show product not available at a location or not.
$query->orWhereNull('VLD.location_id');
});
}
}
)
->where('p.business_id', $business_id)
->where('p.type', '!=', 'modifier')
->where('p.is_inactive', 0)
->where('p.not_for_selling', 0)
//Hide products not available in the selected location
->where(function ($q) use ($location_id) {
$q->where('pl.location_id', $location_id);
});
//Include search
if (!empty($term)) {
$products->where(function ($query) use ($term) {
$query->where('p.name', 'like', '%' . $term . '%');
$query->orWhere('sku', 'like', '%' . $term . '%');
$query->orWhere('sub_sku', 'like', '%' . $term . '%');
});
}
//Include check for quantity
if ($check_qty) {
$products->where('VLD.qty_available', '>', 0);
}
if (!empty($category_id) && ($category_id != 'all')) {
$products->where(function ($query) use ($category_id) {
$query->where('p.category_id', $category_id);
$query->orWhere('p.sub_category_id', $category_id);
});
}
if (!empty($brand_id) && ($brand_id != 'all')) {
$products->where('p.brand_id', $brand_id);
}
if (!empty($request->get('is_enabled_stock'))) {
$is_enabled_stock = 0;
if ($request->get('is_enabled_stock') == 'product') {
$is_enabled_stock = 1;
}
$products->where('p.enable_stock', $is_enabled_stock);
}
if (!empty($request->get('repair_model_id'))) {
$products->where('p.repair_model_id', $request->get('repair_model_id'));
}
$products = $products->select(
'p.id as product_id',
'p.name',
'p.type',
'p.enable_stock',
'p.image as product_image',
'variations.id',
'variations.name as variation',
'VLD.qty_available',
'variations.default_sell_price as selling_price',
'variations.sub_sku',
'u.short_name as unit'
)
->with(['media', 'group_prices'])
->orderBy('p.name', 'asc')
->paginate(50);
$price_groups = SellingPriceGroup::where('business_id', $business_id)->active()->pluck('name', 'id');
$allowed_group_prices = [];
foreach ($price_groups as $key => $value) {
if (auth()->user()->can('selling_price_group.' . $key)) {
$allowed_group_prices[$key] = $value;
}
}
$show_prices = !empty($pos_settings['show_pricing_on_product_sugesstion']);
return view('sale_pos.partials.product_list')
->with(compact('products', 'allowed_group_prices', 'show_prices'));
}
}
/**
* Shows invoice url.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function showInvoiceUrl($id)
{
// if (!auth()->user()->can('sell.update')) {
// abort(403, 'Unauthorized action.');
// }
if (request()->ajax()) {
$business_id = request()->session()->get('user.business_id');
$transaction = Transaction::where('business_id', $business_id)
->findorfail($id);
$url = $this->transactionUtil->getInvoiceUrl($id, $business_id);
return view('sale_pos.partials.invoice_url_modal')
->with(compact('transaction', 'url'));
}
}
/**
* Shows invoice to guest user.
*
* @param string $token
* @return \Illuminate\Http\Response
*/
public function showInvoice($token)
{
$transaction = Transaction::where('invoice_token', $token)->with(['business', 'location'])->first();
if (!empty($transaction)) {
$invoice_layout_id = $transaction->is_direct_sale ? $transaction->location->sale_invoice_layout_id : null;
$receipt = $this->receiptContent($transaction->business_id, $transaction->location_id, $transaction->id, 'browser', false, false, $invoice_layout_id);
$pos_settings = empty($transaction->business->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($transaction->business->pos_settings, true);
$payment_link = '';
if (!empty($pos_settings['enable_payment_link']) && $transaction->payment_status != 'paid') {
$payment_link = $this->transactionUtil->getInvoicePaymentLink($transaction->id, $transaction->business_id);
}
$title = $transaction->business->name . ' | ' . $transaction->invoice_no;
return view('sale_pos.partials.show_invoice')
->with(compact('receipt', 'title', 'payment_link'));
} else {
exit(__('messages.something_went_wrong'));
}
}
/**
* Allows payment for the invoice by guest user.
*
* @param string $token
* @return \Illuminate\Http\Response
*/
public function invoicePayment($token)
{
$transaction = Transaction::where('invoice_token', $token)->with(['business', 'contact', 'location'])->first();
$business = $transaction->business;
$business_details = $this->businessUtil->getDetails($business->id);
$pos_settings = empty($business->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business->pos_settings, true);
if (!empty($transaction) && $transaction->status == 'final' && !empty($pos_settings['enable_payment_link'])) {
$title = $transaction->business->name . ' | ' . $transaction->invoice_no;
$paid_amount = $this->transactionUtil->getTotalPaid($transaction->id);
$total_payable = $transaction->final_total - $paid_amount;
$total_payable_formatted = $this->transactionUtil->num_f($total_payable, true, $business_details);
$date_formatted = $this->transactionUtil->format_date($transaction->transaction_date, true, $business_details);
$total_amount = $this->transactionUtil->num_f($transaction->final_total, true, $business_details);
$total_paid = $this->transactionUtil->num_f($paid_amount, true, $business_details);
return view('sale_pos.partials.guest_payment_form')
->with(compact('transaction', 'title', 'pos_settings', 'total_payable', 'total_payable_formatted', 'date_formatted', 'total_amount', 'total_paid', 'business_details'));
} else {
exit(__('messages.something_went_wrong'));
}
}
public function pay_razorpay($transaction, $total_payable, $request)
{
$pos_settings = empty($transaction->business->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($transaction->business->pos_settings, true);
$razorpay_payment_id = $request->razorpay_payment_id;
$razorpay_api = new Api($pos_settings['razor_pay_key_id'], $pos_settings['razor_pay_key_secret']);
$payment = $razorpay_api->payment->fetch($razorpay_payment_id)->capture(['amount' => $total_payable * 100]); // Captures a payment
if (empty($payment->error_code)) {
return $payment->id;
} else {
$error_description = $payment->error_description;
\Log::emergency($payment->error_description);
throw new \Exception($error_description);
}
}
public function pay_stripe($transaction, $total_payable, $request)
{
$pos_settings = empty($transaction->business->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($transaction->business->pos_settings, true);
Stripe::setApiKey($pos_settings['stripe_secret_key']);
$metadata = ['stripe_email' => $request->stripeEmail];
$business_details = $this->businessUtil->getDetails($transaction->business->id);
$charge = Charge::create([
'amount' => $total_payable * 100,
'currency' => strtolower($business_details->currency_code),
'source' => $request->stripeToken,
'metadata' => $metadata,
]);
return $charge->id;
}
public function confirmPayment($id, Request $request)
{
try {
$transaction = Transaction::with(['business'])->find($id);
$transaction_before = $transaction->replicate();
$payment_link = $this->transactionUtil->getInvoicePaymentLink($transaction->id, $transaction->business_id);
$paid_amount = $this->transactionUtil->getTotalPaid($transaction->id);
$total_payable = $transaction->final_total - $paid_amount;
$pay_function = 'pay_' . $request->gateway;
$payment_id = $this->$pay_function($transaction, $total_payable, $request);
if (!empty($payment_id)) {
DB::beginTransaction();
$ref_count = $this->transactionUtil->setAndGetReferenceCount('sell_payment', $transaction->business_id);
$payment_ref_no = $this->transactionUtil->generateReferenceNumber('sell_payment', $ref_count, $transaction->business_id);
$data = [
'paid_on' => \Carbon::now()->toDateTimeString(),
'transaction_id' => $transaction->id,
'amount' => $total_payable,
'payment_for' => $transaction->contact_id,
'method' => 'cash',
'note' => $payment_id,
'paid_through_link' => 1,
'gateway' => $request->gateway,
'business_id' => $transaction->business_id,
'payment_ref_no' => $payment_ref_no,
];
$tp = TransactionPayment::create($data);
$payment_status = $this->transactionUtil->updatePaymentStatus($transaction->id, $transaction->final_total);
$transaction->payment_status = $payment_status;
$this->transactionUtil->activityLog($transaction, 'payment_edited', $transaction_before);
DB::commit();
$output = [
'success' => 1,
'msg' => __('purchase.payment_added_success'),
];
} else {
$output = [
'success' => 0,
'msg' => __('messages.something_went_wrong'),
];
}
} catch (\Exception $e) {
DB::rollBack();
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output = [
'success' => 0,
'msg' => __('messages.something_went_wrong'),
];
}
return redirect($payment_link)->with('status', $output);
}
/**
* Display a listing of the recurring invoices.
*
* @return \Illuminate\Http\Response
*/
public function listSubscriptions()
{
if (!auth()->user()->can('sell.view') && !auth()->user()->can('direct_sell.access')) {
abort(403, 'Unauthorized action.');
}
if (request()->ajax()) {
$business_id = request()->session()->get('user.business_id');
$sells = Transaction::leftJoin('contacts', 'transactions.contact_id', '=', 'contacts.id')
->leftJoin('transaction_payments as tp', 'transactions.id', '=', 'tp.transaction_id')
->join(
'business_locations AS bl',
'transactions.location_id',
'=',
'bl.id'
)
->where('transactions.business_id', $business_id)
->where('transactions.type', 'sell')
->where('transactions.status', 'final')
->where('transactions.is_recurring', 1)
->select(
'transactions.id',
'transactions.transaction_date',
'transactions.is_direct_sale',
'transactions.invoice_no',
'contacts.name',
'transactions.subscription_no',
'bl.name as business_location',
'transactions.recur_parent_id',
'transactions.recur_stopped_on',
'transactions.is_recurring',
'transactions.recur_interval',
'transactions.recur_interval_type',
'transactions.recur_repetitions',
'transactions.subscription_repeat_on'
)->with(['subscription_invoices']);
$permitted_locations = auth()->user()->permitted_locations();
if ($permitted_locations != 'all') {
$sells->whereIn('transactions.location_id', $permitted_locations);
}
if (!empty(request()->start_date) && !empty(request()->end_date)) {
$start = request()->start_date;
$end = request()->end_date;
$sells->whereDate('transactions.transaction_date', '>=', $start)
->whereDate('transactions.transaction_date', '<=', $end);
}
if (!empty(request()->contact_id)) {
$sells->where('transactions.contact_id', request()->contact_id);
}
$datatable = Datatables::of($sells)
->addColumn(
'action',
function ($row) {
$html = '';
if ($row->is_recurring == 1 && auth()->user()->can('sell.update')) {
$link_text = !empty($row->recur_stopped_on) ? __('lang_v1.start_subscription') : __('lang_v1.stop_subscription');
$link_class = !empty($row->recur_stopped_on) ? 'btn-success' : 'btn-danger';
$html .= '<a href="' . action([\App\Http\Controllers\SellPosController::class, 'toggleRecurringInvoices'], [$row->id]) . '" class="toggle_recurring_invoice btn btn-xs ' . $link_class . '"><i class="fa fa-power-off"></i> ' . $link_text . '</a>';
if ($row->is_direct_sale == 0) {
$html .= '<a target="_blank" class="tw-dw-btn tw-dw-btn-xs tw-dw-btn-outline tw-dw-btn-primary" href="' . action([\App\Http\Controllers\SellPosController::class, 'edit'], [$row->id]) . '"><i class="glyphicon glyphicon-edit"></i> ' . __('messages.edit') . '</a>';
} else {
$html .= '<a target="_blank" class="tw-dw-btn tw-dw-btn-xs tw-dw-btn-outline tw-dw-btn-primary" href="' . action([\App\Http\Controllers\SellController::class, 'edit'], [$row->id]) . '"><i class="glyphicon glyphicon-edit"></i> ' . __('messages.edit') . '</a>';
}
if (auth()->user()->can('direct_sell.delete') || auth()->user()->can('sell.delete')) {
$html .= '&nbsp;<a href="' . action([\App\Http\Controllers\SellPosController::class, 'destroy'], [$row->id]) . '" class="delete-sale tw-dw-btn tw-dw-btn-outline tw-dw-btn-xs tw-dw-btn-error"><i class="fas fa-trash"></i> ' . __('messages.delete') . '</a>';
}
}
return $html;
}
)
->removeColumn('id')
->editColumn('transaction_date', '@format_date($transaction_date)')
->editColumn('recur_interval', function ($row) {
$type = $row->recur_interval == 1 ? Str::singular(__('lang_v1.' . $row->recur_interval_type)) : __('lang_v1.' . $row->recur_interval_type);
$recur_interval = $row->recur_interval . $type;
if ($row->recur_interval_type == 'months' && !empty($row->subscription_repeat_on)) {
$recur_interval .= '<br><small class="text-muted">' .
__('lang_v1.repeat_on') . ': ' . str_ordinal($row->subscription_repeat_on);
}
return $recur_interval;
})
->editColumn('recur_repetitions', function ($row) {
return !empty($row->recur_repetitions) ? $row->recur_repetitions : '-';
})
->addColumn('subscription_invoices', function ($row) {
$invoices = [];
if (!empty($row->subscription_invoices)) {
$invoices = $row->subscription_invoices->pluck('invoice_no')->toArray();
}
$html = '';
$count = 0;
if (!empty($invoices)) {
$imploded_invoices = '<span class="label bg-info">' . implode('</span>, <span class="label bg-info">', $invoices) . '</span>';
$count = count($invoices);
$html .= '<small>' . $imploded_invoices . '</small>';
}
if ($count > 0) {
$html .= '<br><small class="text-muted">' .
__('sale.total') . ': ' . $count . '</small>';
}
return $html;
})
->addColumn('last_generated', function ($row) {
if (!empty($row->subscription_invoices)) {
$last_generated_date = $row->subscription_invoices->max('created_at');
}
return !empty($last_generated_date) ? $last_generated_date->diffForHumans() : '';
})
->addColumn('upcoming_invoice', function ($row) {
if (empty($row->recur_stopped_on)) {
$last_generated = !empty(count($row->subscription_invoices))?\Carbon::parse($row->subscription_invoices->max('transaction_date')) : \Carbon::parse($row->transaction_date);
$last_generated_string = $last_generated->format('Y-m-d');
$last_generated = \Carbon::parse($last_generated_string);
if ($row->recur_interval_type == 'days') {
$upcoming_invoice = $last_generated->addDays($row->recur_interval);
} elseif ($row->recur_interval_type == 'months') {
if (!empty($row->subscription_repeat_on)) {
$last_generated_string = $last_generated->format('Y-m');
$last_generated = \Carbon::parse($last_generated_string . '-' . $row->subscription_repeat_on);
}
$upcoming_invoice = $last_generated->addMonths($row->recur_interval);
} elseif ($row->recur_interval_type == 'years') {
$upcoming_invoice = $last_generated->addYears($row->recur_interval);
}
}
return !empty($upcoming_invoice) ? $this->transactionUtil->format_date($upcoming_invoice) : '';
})
->rawColumns(['action', 'subscription_invoices', 'recur_interval'])
->make(true);
return $datatable;
}
return view('sale_pos.subscriptions');
}
/**
* Starts or stops a recurring invoice.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function toggleRecurringInvoices($id)
{
if (!auth()->user()->can('sell.create')) {
abort(403, 'Unauthorized action.');
}
try {
$business_id = request()->session()->get('user.business_id');
$transaction = Transaction::where('business_id', $business_id)
->where('type', 'sell')
->where('is_recurring', 1)
->findorfail($id);
if (empty($transaction->recur_stopped_on)) {
$transaction->recur_stopped_on = \Carbon::now();
} else {
$transaction->recur_stopped_on = null;
}
$transaction->save();
$output = ['success' => 1,
'msg' => trans('lang_v1.updated_success'),
];
} catch (\Exception $e) {
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output = ['success' => 0,
'msg' => trans('messages.something_went_wrong'),
];
}
return $output;
}
public function getRewardDetails(Request $request)
{
if ($request->session()->get('business.enable_rp') != 1) {
return '';
}
$business_id = request()->session()->get('user.business_id');
$customer_id = $request->input('customer_id');
$redeem_details = $this->transactionUtil->getRewardRedeemDetails($business_id, $customer_id);
return json_encode($redeem_details);
}
public function placeOrdersApi(Request $request)
{
try {
$api_token = $request->header('API-TOKEN');
$api_settings = $this->moduleUtil->getApiSettings($api_token);
$business_id = $api_settings->business_id;
$location_id = $api_settings->location_id;
$input = $request->only(['products', 'customer_id', 'addresses']);
//check if all stocks are available
$variation_ids = [];
foreach ($input['products'] as $product_data) {
$variation_ids[] = $product_data['variation_id'];
}
$variations_details = $this->getVariationsDetails($business_id, $location_id, $variation_ids);
$is_valid = true;
$error_messages = [];
$sell_lines = [];
$final_total = 0;
foreach ($variations_details as $variation_details) {
if ($variation_details->product->enable_stock == 1) {
if (empty($variation_details->variation_location_details[0]) || $variation_details->variation_location_details[0]->qty_available < $input['products'][$variation_details->id]['quantity']) {
$is_valid = false;
$error_messages[] = 'Only ' . $variation_details->variation_location_details[0]->qty_available . ' ' . $variation_details->product->unit->short_name . ' of ' . $input['products'][$variation_details->id]['product_name'] . ' available';
}
}
//Create product line array
$sell_lines[] = [
'product_id' => $variation_details->product->id,
'unit_price_before_discount' => $variation_details->unit_price_inc_tax,
'unit_price' => $variation_details->unit_price_inc_tax,
'unit_price_inc_tax' => $variation_details->unit_price_inc_tax,
'variation_id' => $variation_details->id,
'quantity' => $input['products'][$variation_details->id]['quantity'],
'item_tax' => 0,
'enable_stock' => $variation_details->product->enable_stock,
'tax_id' => null,
];
$final_total += ($input['products'][$variation_details->id]['quantity'] * $variation_details->unit_price_inc_tax);
}
if (!$is_valid) {
return $this->respond([
'success' => false,
'error_messages' => $error_messages,
]);
}
$business = Business::find($business_id);
$user_id = $business->owner_id;
$business_data = [
'id' => $business_id,
'accounting_method' => $business->accounting_method,
'location_id' => $location_id,
];
$customer = Contact::where('business_id', $business_id)
->whereIn('type', ['customer', 'both'])
->find($input['customer_id']);
$order_data = [
'business_id' => $business_id,
'location_id' => $location_id,
'contact_id' => $input['customer_id'],
'final_total' => $final_total,
'created_by' => $user_id,
'status' => 'final',
'payment_status' => 'due',
'additional_notes' => '',
'transaction_date' => \Carbon::now(),
'customer_group_id' => $customer->customer_group_id,
'tax_rate_id' => null,
'sale_note' => null,
'commission_agent' => null,
'order_addresses' => json_encode($input['addresses']),
'products' => $sell_lines,
'is_created_from_api' => 1,
'discount_type' => 'fixed',
'discount_amount' => 0,
];
$invoice_total = [
'total_before_tax' => $final_total,
'tax' => 0,
];
DB::beginTransaction();
$transaction = $this->transactionUtil->createSellTransaction($business_id, $order_data, $invoice_total, $user_id, false);
//Create sell lines
$this->transactionUtil->createOrUpdateSellLines($transaction, $order_data['products'], $order_data['location_id'], false, null, [], false);
//update product stock
foreach ($order_data['products'] as $product) {
if ($product['enable_stock']) {
$this->productUtil->decreaseProductQuantity(
$product['product_id'],
$product['variation_id'],
$order_data['location_id'],
$product['quantity']
);
}
}
$this->transactionUtil->mapPurchaseSell($business_data, $transaction->sell_lines, 'purchase');
//Auto send notification
$this->notificationUtil->autoSendNotification($business_id, 'new_sale', $transaction, $transaction->contact);
DB::commit();
$receipt = $this->receiptContent($business_id, $transaction->location_id, $transaction->id);
$output = [
'success' => 1,
'transaction' => $transaction,
'receipt' => $receipt,
];
} catch (\Exception $e) {
DB::rollBack();
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$msg = trans('messages.something_went_wrong');
if (get_class($e) == \App\Exceptions\PurchaseSellMismatch::class) {
$msg = $e->getMessage();
}
if (get_class($e) == \App\Exceptions\AdvanceBalanceNotAvailable::class) {
$msg = $e->getMessage();
}
$output = ['success' => 0,
'error_messages' => [$msg],
];
}
return $this->respond($output);
}
private function getVariationsDetails($business_id, $location_id, $variation_ids)
{
$variation_details = Variation::whereIn('id', $variation_ids)
->with([
'product' => function ($q) use ($business_id) {
$q->where('business_id', $business_id);
},
'product.unit',
'variation_location_details' => function ($q) use ($location_id) {
$q->where('location_id', $location_id);
},
])->get();
return $variation_details;
}
public function getTypesOfServiceDetails(Request $request)
{
$location_id = $request->input('location_id');
$types_of_service_id = $request->input('types_of_service_id');
$business_id = $request->session()->get('user.business_id');
$types_of_service = TypesOfService::where('business_id', $business_id)
->where('id', $types_of_service_id)
->first();
$price_group_id = !empty($types_of_service->location_price_group[$location_id])
? $types_of_service->location_price_group[$location_id] : '';
$price_group_name = '';
if (!empty($price_group_id)) {
$price_group = SellingPriceGroup::find($price_group_id);
$price_group_name = $price_group->name;
}
$modal_html = view('types_of_service.pos_form_modal')
->with(compact('types_of_service'))->render();
return $this->respond([
'price_group_id' => $price_group_id,
'packing_charge' => $types_of_service->packing_charge,
'packing_charge_type' => $types_of_service->packing_charge_type,
'modal_html' => $modal_html,
'price_group_name' => $price_group_name,
]);
}
private function __getwarranties()
{
$business_id = session()->get('user.business_id');
$common_settings = session()->get('business.common_settings');
$is_warranty_enabled = !empty($common_settings['enable_product_warranty']) ? true : false;
$warranties = $is_warranty_enabled ? Warranty::forDropdown($business_id) : [];
return $warranties;
}
/**
* Parse the weighing barcode.
*
* @return array
*/
private function __parseWeighingBarcode($scale_barcode)
{
$business_id = session()->get('user.business_id');
$scale_setting = session()->get('business.weighing_scale_setting');
$error_msg = trans('messages.something_went_wrong');
//Check for prefix.
if ((strlen($scale_setting['label_prefix']) == 0) || Str::startsWith($scale_barcode, $scale_setting['label_prefix'])) {
$scale_barcode = substr($scale_barcode, strlen($scale_setting['label_prefix']));
//Get product sku, trim left side 0
$sku = ltrim(substr($scale_barcode, 0, $scale_setting['product_sku_length'] + 1), '0');
//Get quantity integer
$qty_int = substr($scale_barcode, $scale_setting['product_sku_length'] + 1, $scale_setting['qty_length'] + 1);
//Get quantity decimal
$qty_decimal = '0.' . substr($scale_barcode, $scale_setting['product_sku_length'] + $scale_setting['qty_length'] + 2, $scale_setting['qty_length_decimal'] + 1);
$qty = (float) $qty_int + (float) $qty_decimal;
//Find the variation id
$result = $this->productUtil->filterProduct($business_id, $sku, null, false, null, [], ['sub_sku'], false, 'exact')->first();
if (!empty($result)) {
return ['variation_id' => $result->variation_id,
'qty' => $qty,
'success' => true,
];
} else {
$error_msg = trans('lang_v1.sku_not_match', ['sku' => $sku]);
}
} else {
$error_msg = trans('lang_v1.prefix_did_not_match');
}
return [
'success' => false,
'msg' => $error_msg,
];
}
public function getFeaturedProducts($id)
{
$location = BusinessLocation::findOrFail($id);
$featured_products = $location->getFeaturedProducts();
if (!empty($featured_products)) {
return view('sale_pos.partials.featured_products')->with(compact('featured_products'));
} else {
return '';
}
}
/**
* Converts drafts and quotations to invoice
*/
public function convertToInvoice($id)
{
if (!auth()->user()->can('sell.create') && !auth()->user()->can('direct_sell.access')) {
abort(403, 'Unauthorized action.');
}
try {
$business_id = request()->session()->get('user.business_id');
$transaction = Transaction::with(['sell_lines',
'sell_lines.product',
'sell_lines.variations',
'contact'])
->where('business_id', $business_id)
->where('status', 'draft')
->findOrFail($id);
$transaction_before = $transaction->replicate();
$is_direct_sale = $transaction->is_direct_sale;
//Check Customer credit limit
$data = [
'final_total' => $transaction->final_total,
'contact_id' => $transaction->contact_id,
'status' => 'final',
];
$is_credit_limit_exeeded = $this->transactionUtil->isCustomerCreditLimitExeeded($data, $id);
if ($is_credit_limit_exeeded !== false) {
$credit_limit_amount = $this->transactionUtil->num_f($is_credit_limit_exeeded, true);
$output = ['success' => 0,
'msg' => __('lang_v1.cutomer_credit_limit_exeeded', ['credit_limit' => $credit_limit_amount]),
];
return redirect()
->back()
->with('status', $output);
}
DB::beginTransaction();
//Check if there is a open register, if no then redirect to Create Register screen.
if (!$is_direct_sale && $this->cashRegisterUtil->countOpenedRegister() == 0) {
return redirect()->action([\App\Http\Controllers\CashRegisterController::class, 'create']);
}
$invoice_no = $this->transactionUtil->getInvoiceNumber($business_id, 'final', $transaction->location_id);
$transaction->invoice_no = $invoice_no;
$transaction->transaction_date = \Carbon::now();
$transaction->status = 'final';
$transaction->sub_status = null;
$transaction->is_quotation = 0;
$transaction->save();
//update product stock
foreach ($transaction->sell_lines as $sell_line) {
$decrease_qty = $sell_line->quantity;
if ($sell_line->product->enable_stock == 1) {
$this->productUtil->decreaseProductQuantity(
$sell_line->product_id,
$sell_line->variation_id,
$transaction->location_id,
$decrease_qty
);
}
if ($sell_line->product->type == 'combo') {
//Decrease quantity of combo as well.
$combo_variations = $sell_line->variations->combo_variations;
foreach ($combo_variations as $key => $value) {
$base_unit_multiplier = 1;
if (!empty($value['unit_id'])) {
$unit = Unit::find($value['unit_id']);
$base_unit_multiplier = !empty($unit->base_unit_multiplier) ? $unit->base_unit_multiplier : $base_unit_multiplier;
}
$combo_variations[$key]['product_id'] = $sell_line->product_id;
$combo_variations[$key]['product_id'] = $sell_line->product_id;
$combo_variations[$key]['quantity'] = $value['quantity'] * $decrease_qty * $base_unit_multiplier;
}
$this->productUtil
->decreaseProductQuantityCombo(
$combo_variations,
$transaction->location_id
);
}
}
//Update payment status
$this->transactionUtil->updatePaymentStatus($transaction->id, $transaction->final_total);
$business_details = $this->businessUtil->getDetails($business_id);
$pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true);
$business = ['id' => $business_id,
'accounting_method' => request()->session()->get('business.accounting_method'),
'location_id' => $transaction->location_id,
'pos_settings' => $pos_settings,
];
try {
$this->transactionUtil->mapPurchaseSell($business, $transaction->sell_lines, 'purchase');
} catch (\Exception $e) {
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$msg = trans('messages.something_went_wrong');
if (get_class($e) == \App\Exceptions\PurchaseSellMismatch::class) {
$msg = $e->getMessage();
}
if (get_class($e) == \App\Exceptions\AdvanceBalanceNotAvailable::class) {
$msg = $e->getMessage();
}
$output = ['success' => 0,
'msg' => $msg,
];
return redirect()
->action([\App\Http\Controllers\SellController::class, 'index'])
->with('status', $output);
}
//Auto send notification
$this->notificationUtil->autoSendNotification($business_id, 'new_sale', $transaction, $transaction->contact);
$this->transactionUtil->activityLog($transaction, 'edited', $transaction_before);
DB::commit();
$output = ['success' => 1, 'msg' => __('lang_v1.converted_to_invoice_successfully', ['invoice_no' => $transaction->invoice_no])];
} catch (Exception $e) {
DB::rollBack();
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$msg = trans('messages.something_went_wrong');
if (get_class($e) == \App\Exceptions\PurchaseSellMismatch::class) {
$msg = $e->getMessage();
}
if (get_class($e) == \App\Exceptions\AdvanceBalanceNotAvailable::class) {
$msg = $e->getMessage();
}
$output = ['success' => 0,
'msg' => $msg,
];
}
return redirect()
->action([\App\Http\Controllers\SellController::class, 'index'])
->with('status', $output);
}
/**
* Converts drafts and quotations to invoice
*/
public function convertToProforma($id)
{
if (!auth()->user()->can('sell.create') && !auth()->user()->can('direct_sell.access')) {
abort(403, 'Unauthorized action.');
}
try {
$business_id = request()->session()->get('user.business_id');
$transaction = Transaction::where('business_id', $business_id)
->where('status', 'draft')
->findOrFail($id);
$transaction_before = $transaction->replicate();
$transaction->sub_status = 'proforma';
$transaction->save();
$this->transactionUtil->activityLog($transaction, 'edited', $transaction_before);
$output = ['success' => 1, 'msg' => __('lang_v1.converted_to_proforma_successfully')];
} catch (Exception $e) {
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output = ['success' => 0,
'msg' => trans('messages.something_went_wrong'),
];
}
return $output;
}
/**
* Copy quotation
*
*/
public function copyQuotation($id)
{
if (!auth()->user()->can('quotation.update')) {
abort(403, 'Unauthorized action.');
}
try {
$business_id = request()->session()->get('user.business_id');
$transaction = Transaction::where('business_id', $business_id)
->where('sub_status', 'quotation')
->findOrFail($id);
DB::beginTransaction();
$quotation = $transaction->replicate();
$quotation->transaction_date = \Carbon::now()->format('Y-m-d H:i:s');
$quotation->invoice_no = $this->transactionUtil->getInvoiceNumber($business_id, 'draft',
$transaction->location_id);
$quotation->save();
$sell_lines = TransactionSellLine::where('transaction_id', $transaction->id)->get();
$new_sell_lines = [];
foreach ($sell_lines as $sell_line) {
$sl = $sell_line->replicate();
$sl->transaction_id = $quotation->id;
$new_sell_lines[] = $sl;
}
$quotation->sell_lines()->saveMany($new_sell_lines);
DB::commit();
$output = ['success' => 1, 'msg' => __('lang_v1.converted_to_proforma_successfully')];
} catch (Exception $e) {
DB::rollBack();
\Log::emergency("File:" . $e->getFile() . "Line:" . $e->getLine() . "Message:" . $e->getMessage());
$output = ['success' => 0,
'msg' => trans("messages.something_went_wrong"),
];
}
return redirect()->action([\App\Http\Controllers\SellController::class, 'getQuotations']);
}
/**
* download pdf for given transaction
*/
public function downloadPdf($id)
{
if (!(config('constants.enable_download_pdf') && auth()->user()->can('print_invoice'))) {
abort(403, 'Unauthorized action.');
}
$business_id = request()->session()->get('user.business_id');
$receipt_contents = $this->transactionUtil->getPdfContentsForGivenTransaction($business_id, $id);
$receipt_details = $receipt_contents['receipt_details'];
$location_details = $receipt_contents['location_details'];
$is_email_attachment = false;
$blade_file = 'download_pdf';
if (!empty($receipt_details->is_export)) {
$blade_file = 'download_export_pdf';
}
//Generate pdf
$body = view('sale_pos.receipts.' . $blade_file)
->with(compact('receipt_details', 'location_details', 'is_email_attachment'))
->render();
$mpdf = new \Mpdf\Mpdf(['tempDir' => public_path('uploads/temp'),
'mode' => 'utf-8',
'autoScriptToLang' => true,
'autoLangToFont' => true,
'autoVietnamese' => true,
'autoArabic' => true,
'margin_top' => 8,
'margin_bottom' => 8,
'format' => 'A4',
]);
$mpdf->useSubstitutions = true;
$mpdf->SetWatermarkText($receipt_details->business_name, 0.1);
$mpdf->showWatermarkText = true;
$mpdf->SetTitle('INVOICE-' . $receipt_details->invoice_no . '.pdf');
$mpdf->WriteHTML($body);
$mpdf->Output('INVOICE-' . $receipt_details->invoice_no . '.pdf', 'I');
}
/**
* download pdf for given quotation
*/
public function downloadQuotationPdf($id)
{
if (!(config('constants.enable_download_pdf'))) {
abort(403, 'Unauthorized action.');
}
$business_id = request()->session()->get('user.business_id');
$sub_status = !empty(request()->input('sub_status')) ? request()->input('sub_status') : '';
$receipt_contents = $this->transactionUtil->getPdfContentsForGivenTransaction($business_id, $id);
$receipt_details = $receipt_contents['receipt_details'];
$location_details = $receipt_contents['location_details'];
//Generate pdf
$body = view('sale_pos.receipts.download_quotation_pdf')
->with(compact('receipt_details', 'location_details', 'sub_status'))
->render();
$pdf_name = (!empty($sub_status) && $sub_status == 'proforma') ? __('lang_v1.proforma_invoice') : 'QUOTATION';
$mpdf = new \Mpdf\Mpdf(['tempDir' => public_path('uploads/temp'),
'mode' => 'utf-8',
'autoScriptToLang' => true,
'autoLangToFont' => true,
'autoVietnamese' => true,
'autoArabic' => true,
'margin_top' => 8,
'margin_bottom' => 8,
'format' => 'A4',
]);
$mpdf->useSubstitutions = true;
$mpdf->SetWatermarkText($receipt_details->business_name, 0.1);
$mpdf->showWatermarkText = true;
$mpdf->SetTitle($pdf_name . '-' . $receipt_details->invoice_no . '.pdf');
$mpdf->WriteHTML($body);
$mpdf->Output($pdf_name . '-' . $receipt_details->invoice_no . '.pdf', 'I');
}
/**
* download pdf for given shipment
*/
public function downloadPackingListPdf($id)
{
if (!(config('constants.enable_download_pdf'))) {
abort(403, 'Unauthorized action.');
}
$business_id = request()->session()->get('user.business_id');
$receipt_contents = $this->transactionUtil->getPdfContentsForGivenTransaction($business_id, $id);
$receipt_details = $receipt_contents['receipt_details'];
$location_details = $receipt_contents['location_details'];
//Generate pdf
$body = view('sale_pos.receipts.download_packing_list_pdf')
->with(compact('receipt_details', 'location_details'))
->render();
$mpdf = new \Mpdf\Mpdf(['tempDir' => public_path('uploads/temp'),
'mode' => 'utf-8',
'autoScriptToLang' => true,
'autoLangToFont' => true,
'autoVietnamese' => true,
'autoArabic' => true,
'margin_top' => 8,
'margin_bottom' => 8,
'format' => 'A4',
]);
$mpdf->useSubstitutions = true;
$mpdf->SetWatermarkText($receipt_details->business_name, 0.1);
$mpdf->showWatermarkText = true;
$mpdf->SetTitle('PACKINGSLIP-' . $receipt_details->invoice_no . '.pdf');
$mpdf->WriteHTML($body);
$mpdf->Output('PACKINGSLIP-' . $receipt_details->invoice_no . '.pdf', 'I');
}
public function showServiceStaffAvailibility()
{
$location_id = request()->input('location_id');
$business_id = request()->session()->get('user.business_id');
$service_staffs = $this->productUtil->getServiceStaff($business_id, $location_id);
return view('sale_pos.partials.service_staff_availability_modal')
->with(compact('service_staffs'));
}
public function pauseResumeServiceStaffTimer($user_id)
{
$service_staff = User::find($user_id);
if (empty($service_staff->paused_at)) {
$service_staff->paused_at = \Carbon::now();
} else {
//add diff to available_at
$mins = \Carbon::now()->diffInMinutes(\Carbon::parse($service_staff->paused_at));
$service_staff->available_at = \Carbon::parse($service_staff->available_at)->addMinutes($mins);
$service_staff->paused_at = null;
}
$service_staff->save();
return ['paused_at' => $service_staff->paused_at];
}
public function markAsAvailable($user_id)
{
$service_staff = User::where('id', $user_id)
->update(['paused_at' => null, 'available_at' => null]);
return ['success' => true];
}
public function validateInvoiceToServiceStaffReplacement($invoice_no)
{
$business_id = request()->session()->get('user.business_id');
$transaction = Transaction::where('business_id', $business_id)
->where('invoice_no', $invoice_no);
$transaction = $transaction->first();
if (empty($transaction)) {
return ['success' => 0,
'msg' => trans('lang_v1.sell_not_found'),
];
}
$location_id = $transaction->location_id;
$sell_details = TransactionSellLine::join(
'products AS p',
'transaction_sell_lines.product_id',
'=',
'p.id'
)
->join(
'variations AS variations',
'transaction_sell_lines.variation_id',
'=',
'variations.id'
)
->join(
'product_variations AS pv',
'variations.product_variation_id',
'=',
'pv.id'
)
->leftjoin('variation_location_details AS vld', function ($join) use ($location_id) {
$join->on('variations.id', '=', 'vld.variation_id')
->where('vld.location_id', '=', $location_id);
})
->leftjoin('units', 'units.id', '=', 'p.unit_id')
->leftjoin('units as u', 'p.secondary_unit_id', '=', 'u.id')
->where('transaction_sell_lines.transaction_id', $transaction->id)
->select(
DB::raw("IF(pv.is_dummy = 0, CONCAT(p.name, ' (', pv.name, ':',variations.name, ')'), p.name) AS product_name"),
'variations.sub_sku',
'transaction_sell_lines.id',
'transaction_sell_lines.res_service_staff_id',
)
->get();
$enabled_modules = $this->transactionUtil->allModulesEnabled();
$business_details = $this->businessUtil->getDetails($business_id);
$pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true);
$waiters = [];
if ($this->productUtil->isModuleEnabled('service_staff') && !empty($pos_settings['inline_service_staff'])) {
$waiters_enabled = true;
$waiters = $this->productUtil->serviceStaffDropdown($business_id, $location_id);
}
if ($transaction) {
return ['success' => 1,
'msg' => view('sale_pos.partials.service_staff_replacement_modal', compact('transaction', 'sell_details', 'waiters', 'enabled_modules', 'pos_settings'))->render(),
];
}
}
public function change_service_staff($id, Request $request)
{
if (request()->ajax()) {
try {
$business_id = request()->session()->get('user.business_id');
$transaction = Transaction::where('business_id', $business_id)
->where('id', $id)->findOrFail($id);
DB::beginTransaction();
$transaction->res_waiter_id = $request->res_waiter_id;
$transaction->update();
if ($request->sell_details) {
foreach ($request->sell_details as $sell) {
TransactionSellLine::where('id', $sell['id'])->update([
'res_service_staff_id' => $sell['res_service_staff_id'],
]);
}
}
DB::commit();
$output = ['success' => 1,
'msg' => __('lang_v1.updated_success'),
];
} catch (\Exception $e) {
DB::rollBack();
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());
$output = ['success' => 0,
'msg' => __('messages.something_went_wrong'),
];
}
return $output;
}
}
}