surveyRepository = $surveyRepository; } public function getList(array $params = [], $all = false) { $user = auth()->user(); $location_id = @$user->employee->location_id; $sortColumn = [ 'number' => 'pos_invoices.number', 'time' => 'pos_invoices.time', 'total' => 'pos_invoices.total', 'location.name' => 'locations.name', 'customer' => 'customers.name', 'sales.name' => 'sales.name', 'sales2.name' => 'sales.name', 'user.name' => 'users.name' ]; $limit = @$params["limit"] ? (int) @$params["limit"] : 10; $sortColumn = @$params["sort"]["column"] ? $sortColumn[$params["sort"]["column"]] : "pos_invoices.id"; $sortDir = @$params["sort"]["dir"] ? $params["sort"]["dir"] : "desc"; $results = PosInvoice::selectRaw(" pos_invoices.*, CASE WHEN a.first_transaction is not null THEN 1 ELSE 0 END as new_customer, CASE WHEN (a.first_transaction IS NOT NULL AND customers.number NOT ILIKE 'CAG%') OR customers.number = 'NONAME' THEN 'online' ELSE 'offline' END as type" ) ->leftJoin(DB::raw("(select min(pos_invoices.id) as first_transaction from pos_invoices group by customer_id) as a"), 'a.first_transaction', 'pos_invoices.id') ->leftJoin('transactions', 'transactions.invoice_id', 'pos_invoices.id') ->leftJoin('locations', 'locations.id', 'pos_invoices.location_id') ->leftJoin('customers', 'customers.id', 'pos_invoices.customer_id') ->leftJoin('users', 'users.id', 'pos_invoices.user_id') ->leftJoin('sales', 'sales.id', 'pos_invoices.sales_id') ->leftJoin('sales as sales2', 'sales2.id', 'pos_invoices.sales2_id') ->when(@$params["search"], function ($query) use ($params) { $query->where(function ($query) use ($params) { $query->where("pos_invoices.number", "ilike", "%" . $params["search"] . "%") ->orWhere("sales.name", "ilike", "%" . $params["search"] . "%") ->orWhere("sales2.name", "ilike", "%" . $params["search"] . "%") ->orWhere("users.name", "ilike", "%" . $params["search"] . "%") ->orWhere("customers.name", "ilike", "%" . $params["search"] . "%") ->orWhere("customers.phone", "ilike", "%" . $params["search"] . "%"); /* ->orWhereExists(function($subquery) use ($params) { $subquery->select(DB::raw(1)) ->from('pos_invoice_details') ->leftJoin('items', 'pos_invoice_details.item_id', 'items.id') ->leftJoin('item_variants', 'item_variants.id', 'pos_invoice_details.item_variant_id') ->leftJoin('item_reference', 'item_reference.id', 'pos_invoice_details.item_reference_id') ->whereColumn('pos_invoice_details.invoice_id', 'pos_invoices.id') ->where(function($q) use ($params) { $q->where('item_reference.number', 'ilike', '%' . $params['search'] . '%') ->orWhere('items.number', 'ilike', '%' . $params['search'] . '%') ->orWhere('items.name', 'ilike', '%' . $params['search'] . '%') ->orWhere('item_variants.description', 'ilike', '%' . $params['search'] . '%') ->orWhere('item_variants.code', 'ilike', '%' . $params['search'] . '%'); }); }); */ }); }) ->when(!$all, function ($query) use ($location_id) { $query->where("pos_invoices.location_id", $location_id); }) ->when(@$params['active'], function($query) { $query->whereNull('canceled_at'); }) ->when(@$params['filter'], function ($query) use ($params) { foreach ($params['filter'] as $filter) { if ($filter['column'] == 'start') { $query->whereDate("pos_invoices.time", ">=", $filter["query"]); } else if ($filter['column'] == 'end') { $query->whereDate("pos_invoices.time", "<=", $filter["query"]); } else if ($filter['column'] == 'sync') { if ($filter['query'] == 'BERHASIL') { $query->whereNotNull('pos_invoices.sync_at'); } if ($filter['query'] == 'GAGAL') { $query->whereNull('pos_invoices.sync_at') ->whereNotNull('pos_invoices.sync_log'); } } else if ($filter['column'] == 'payment_method') { $query->whereRaw("EXISTS (SELECT id FROM pos_invoice_payments WHERE invoice_id = pos_invoices.id AND method = '" . $filter['query'] . "' AND amount > 0)"); } else { $query->where('pos_invoices.' . $filter['column'], $filter['query']); } } }) ->orderBy($sortColumn, $sortDir) ->paginate($limit); return $results; } public function getUnPosted() { return PosInvoice::whereNull("sync_at") ->whereNull("canceled_at") ->take(1000)->get(); } public function createFromOnline($model) { DB::beginTransaction(); try{ $sales = Sales::where("name","ECOMMERCE")->first(); $user = auth()->user(); $posInvoice = PosInvoice::find((int) $model->invoice_id); if ($posInvoice) return; $location = $model->location; $auto = new AutoNumbering([ "type" => "POS", "prefix" => str_replace(" ", "", $location->code), "location_id" => $location->id, "pad" => 12 ]); $number = $auto->getCurrent(); $invoice = new PosInvoice([ "number" => $number, "user_id" => $user->id, "customer_id" => $model->customer_id, "time" => $model->time, "location_id" => $location->id, "sales_id" => (int) @$sales->id, "note" => @$model->note, "subtotal" => $model->subtotal, "voucher" => 0, "discount" => 0, "tax" => 0, "total" => $model->subtotal + $model->shipping_price, "vouchers" => "", "note_internal" => $model->number, ]); $invoice->save(); $model->invoice_id = $invoice->id; $model->save(); foreach ($model->details as $row) { $invoice->details()->create([ 'item_reference_id' => $row->item_reference_id, 'item_id' => $row->item_id, 'item_variant_id' => $row->item_variant_id, 'note' => '', 'qty' => $row->qty, 'unit_price'=> $row->unit_price, 'unit'=> $row->unit, 'reference'=> $row->reference->number, 'discount'=> (int) @$row->discount, 'vat'=> 0, 'total'=> $row->total, 'unit_cost'=> $row->unit_cost ]); } // find biaya expedisi + asuransi $item = ItemReference::where("number","9CBYEXPDAS")->first(); if ($model->shipping_price && $item){ $invoice->details()->create([ 'item_reference_id' => $item->id, 'item_id' => $item->item_id, 'item_variant_id' => $item->item_variant_id, 'note' => $model->courier_company." ".$model->courier_type, 'qty' => 1, 'unit_price' => $model->shipping_price, 'unit' => 'PCS', 'reference' => $item->number, 'discount' => 0, 'vat' => 0, 'total'=> $model->shipping_price, 'unit_cost'=> $model->shipping_price ]); } foreach ($model->payments as $row) { if ($row->method_type == XenditLink::class){ if ($row->status == "PAID"){ $invoice->payments()->create([ "method" => "XENDIT", "amount" => $row->amount, "bank" => "XENDIT", "card_number" => $row->method->uid, "remarks" => @$row->method->payment_method." ".@$row->method->bank_code ]); } } } foreach($model->vouchers as $voucher){ $invoice->payments()->create([ "method" => "VOUCHER", "amount" => $voucher->nominal, "bank" => "VOUCHER", "card_number" => $voucher->number, "remarks" => "" ]); } $model->invoice_id = $invoice->id; $model->save(); $survey = Survey::where("name","AFTER PURCHASE")->first(); if ($survey){ $data = [ "channel" => "pos", "customer_id" => $invoice->customer_id, "invoice_id" => $invoice->id ]; $this->surveyRepository->generateFeedback($survey, $data); } DB::commit(); return $invoice; }catch(\Exception $e){ DB::rollback(); throw $e; } } public function create(array $data) { return DB::transaction(function () use ($data) { $numbering = (array) @DB::select("SELECT id, transaction, location_id, prefix, pad, current FROM numbering WHERE transaction = ? AND location_id = ? FOR UPDATE ", ["POS", $data["location_id"]])[0]; $location = Location::findOrFail($data["location_id"]); if ($numbering == null) { $numbering = DB::table("numbering")->insert([ "transaction" => "POS", "location_id" => $data["location_id"], "prefix" => str_replace(" ", "", $location->code), "pad" => 12, "current" => 0 ]); $numbering = (array) DB::select("SELECT id, transaction, location_id, prefix, pad, current FROM numbering WHERE id = ? FOR UPDATE ", [DB::getPdo()->lastInsertId()])[0]; } if (Carbon::now()->gte("2024-01-22")) { $prefix_number = $numbering["prefix"]; $next_number = $numbering["current"] + 1; $pad_number = $numbering["pad"] - strlen($prefix_number); $number = $prefix_number . str_pad($next_number, $pad_number, 0, STR_PAD_LEFT); DB::statement("UPDATE numbering SET current = current+1 WHERE id = ?", [$numbering["id"]]); } else { $count = DB::select("select last_value from pos_invoices_id_seq")[0]->last_value; $number = "POS" . str_pad($count + 1, 6, 0, STR_PAD_LEFT); } // $count = DB::select("select last_value from pos_invoices_id_seq")[0]->last_value; $data["user_id"] = auth()->user()->id; $data["voucher"] = (float) @$data["voucher"]; $data["time"] = Carbon::now(); $data["vouchers"] = @$data["vouchers"] ? implode(",", @$data["vouchers"]):""; $data["number"] = $number; $subtotal = (float) @$data["subtotal"]; $discount = (float) @$data["discount"]; $invoice = PosInvoice::create($data); $exclude_item_points = []; $i = 0; foreach ($data["details"] as $row) { $i++; $item = Items::find($row["item_id"]); $row["line_no"] = $i; $row["unit_price"] = (float) @$row["unit_price"]; $row["total"] = (float) @$row["total"]; $row["discount"] = (float) @$row["discount"]; $row["vat"] = (float) @$row["vat"]; $row["unit_cost"] = @$item ? $item->unit_cost : 0; $row["line_discount"] = @$row["discount"] * @$row["qty"]; $row["invoice_discount"] = $subtotal == 0 ? 0: ($row["total"] / $subtotal) * $discount; $row["net_price"] = @$row['net_price']; if (@$row['serial_number']){ $this->activateSerialNumber($invoice, @$row['serial_number'],[ "reference" => @$row["reference"], "description" => @$row["description"], "item_number" => @$row["item_number"], "variant_code" => @$row["variant_code"], "brand" => @$item->dimension->brand ]); } $row["pricelist_discount"] = @$row['pricelist_discount']; if (strpos($item->name, "VOUCHER") > -1) { $exp = $row["unit_price"] == 0 ? 3 : 6; $row["reference"] = $this->issueVoucher($invoice, $item->name, $row["reference"], (float) @$row["qty"], $exp); } $invoice->details()->create($row); } // Handle DISCOUNT vouchers - save to pos_invoice_vouchers table if (@$data["discount_vouchers"]) { $this->useDiscountVoucher($invoice, $data["discount_vouchers"]); } // Handle SALE vouchers (payment vouchers) - legacy behavior if (@$data["vouchers"]) $exclude_item_points = $this->useVoucher($invoice, $data["vouchers"]); $this->checkPoint($invoice, $exclude_item_points); foreach ($data["payments"] as $row) { if ($row["amount"] <= 0) continue; $invoice->payments()->create($row); if ($row['method'] == "POINT"){ DB::table("customer_points") ->insert([ "description" => "Use points for payment transaction " . $invoice->number, "point" => ($row["amount"] / 1000) * -1, "customer_id" => $invoice->customer_id, "reference_id" => $invoice->id, "reference_type" => get_class($invoice), "created_at" => Carbon::now() ]); }else if ($row['method'] == "VOUCHER"){ $voucher = Voucher::where('number', $row['card_number'])->first(); if ($voucher){ // Validate voucher is SALE type for payment method if (($voucher->calculation_type ?? 'SALE') != 'SALE') { throw ValidationException::withMessages([ "voucher" => "Voucher ini adalah Voucher Discount, bukan Voucher Belanja. Tidak bisa digunakan sebagai pembayaran." ]); } $voucher->used_at = Carbon::now(); $voucher->reference_used_id = $invoice->id; $voucher->reference_used_type = get_class($invoice); $voucher->save(); } } } $luckyWheel = LuckyWheel::where("valid_at","<=", Carbon::now()) ->where("expired_at",">=", Carbon::now()) ->where(function($query) use ($location){ $query->whereNull("location_id") ->orWhere("location_id", @$location->id); }) ->where("min_sales_total","<", $invoice->total) ->first(); if ($luckyWheel){ $ticket_counts = $luckyWheel->tickets()->count(); if ($ticket_counts < $luckyWheel->max_ticket || $luckyWheel->max_ticket == -1){ $max_prize = $luckyWheel->max_prize; $current_prize = $luckyWheel->gets()->whereNotNull("redeem_at")->sum("nominal"); // if ($max_prize > $current_prize){ // $ticket2 = $luckyWheel->tickets()->where("customer_id", $invoice->customer_id)->first(); // if ($ticket2 == null){ $ticket = $luckyWheel->tickets()->create([ "invoice_id" => $invoice->id, "customer_id" => $invoice->customer_id, "max_times" => 10 ]); // } // } } } $survey = Survey::where("name","AFTER PURCHASE")->first(); if ($survey){ $data = [ "channel" => "pos", "customer_id" => $invoice->customer_id, "invoice_id" => $invoice->id ]; $feedback = $this->surveyRepository->generateFeedback($survey, $data); if ($feedback){ $notif = new SurveyBroadcast($feedback); $invoice->customer->notify($notif); } } return $invoice; }); } public function deactivateSerialNumber($invoice, $sn){ if (!@$sn) return; $arr = explode(",",$sn); SerialNumberDetail::whereIn("number",$arr)->update([ "activated_at" => null ]); SerialNumberLog::whereIn('number',$arr)->delete(); } public function activateSerialNumber($invoice, $sn, $data){ if (!@$sn) return; $user = auth()->user(); $arr = explode(",",$sn); foreach($arr as $row){ $snDetail = SerialNumberDetail::where("number",$row)->first(); if ($snDetail){ $snDetail->activated_at = Carbon::now(); $snDetail->activated_by = $user->id; $snDetail->invoice_id = $invoice->id; $snDetail->save(); } $snLog = new SerialNumberLog; $snLog->number = $row; $snLog->activated_at = Carbon::now(); $snLog->reference_number = $data["reference"] ?? ""; $snLog->item_number = $data["item_number"] ?? ""; $snLog->variant_code = $data["variant_code"] ?? ""; $snLog->description = $data["description"] ?? ""; $snLog->brand = $data["brand"] ?? ""; $snLog->invoice_no = $invoice->number; $snLog->save(); } } public function checkPoint($invoice, $item_ids) { $point = 0; foreach ($invoice->details as $detail) { $discount_items = DB::select("select apply_point from discounts left join discount_items on discounts.id = discount_items.discount_id where item_reference_id = ? and valid_at <= NOW() and expired_at >= NOW() order by discounts.id desc", [@$detail->item_reference_id]); $apply_point = count($discount_items) > 0 ? $discount_items[0]->apply_point : false; $discount = (float) @$detail->discount; $qty = (float) @$detail->qty; if(!$apply_point) { if ($discount > 0){ continue; } if ($detail->unit_price < $detail->item->net_price){ continue; } // skip is has discount from voucher if (in_array($detail->item_reference_id, $item_ids)){ continue; } } $rows = DB::select( "select point from customer_point_rules WHERE item_reference_id = ? AND qty = ?", [$detail->item_reference_id, $detail->qty] ); if (count($rows)) { $row_point = (float) @$rows[0]->point; $detail->point = $row_point * $qty; $detail->save(); $point += $detail->point; } else { $rows_general = DB::select( "select point from customer_point_rules WHERE item_reference_id = ? AND (qty is null or qty = 0)", [$detail->item_reference_id] ); if (count($rows_general)) { $row_point = (float) @$rows_general[0]->point; $detail->point = $row_point * $qty; $detail->save(); $point += $detail->point; } } } if ($point > 0 && $invoice->location->code != "IND") { DB::table("customer_points") ->insert([ "description" => "Get points from transaction " . $invoice->number, "point" => $point, "customer_id" => $invoice->customer_id, "reference_id" => $invoice->id, "reference_type" => get_class($invoice), "created_at" => Carbon::now() ]); } } private function useVoucher($invoice, $vouchers) { $exclude_item_points = []; $arr = explode(",", $vouchers); foreach ($arr as $voucher) { $item = Voucher::where("number", $voucher)->first(); if (!$item) continue; $item_id = $this->checkAffiliatorVoucher($invoice, $item); if (count($item_id) > 0) { $exclude_item_points = array_merge($exclude_item_points, $item_id); } if (@$item->event){ $item_reference_ids = $item->event->items->pluck("id")->toArray(); $exclude_item_points = array_merge($exclude_item_points, $item_reference_ids); } $item->used_at = Carbon::now(); $item->reference_used_id = $invoice->id; $item->reference_used_type = get_class($invoice); $item->save(); } return $exclude_item_points; } private function useDiscountVoucher($invoice, $discount_vouchers) { // discount_vouchers is an array of voucher codes foreach ($discount_vouchers as $voucher_code) { $voucher = Voucher::where("number", $voucher_code)->first(); if (!$voucher) continue; // Validate it's a DISCOUNT type voucher if (($voucher->calculation_type ?? 'SALE') != 'DISCOUNT') { throw ValidationException::withMessages([ "discount_voucher" => "Voucher {$voucher_code} bukan Voucher Discount." ]); } // Create pos_invoice_vouchers record \App\Models\PosInvoiceVoucher::create([ 'pos_invoice_id' => $invoice->id, 'voucher_id' => $voucher->id, 'nominal' => $voucher->nominal ]); // Mark voucher as used $voucher->used_at = Carbon::now(); $voucher->reference_used_id = $invoice->id; $voucher->reference_used_type = get_class($invoice); $voucher->save(); } } private function findDetail($invoice, $item_reference_ids){ return $invoice->details->filter(function($detail) use ($item_reference_ids){ return in_array($detail->item_reference_id , $item_reference_ids); })->reduce(function($acc, $detail){ return $acc + ($detail->unit_price - $detail->unit_cost); },0); } public function checkAffiliatorVoucher($invoice, $voucher){ if ($voucher->reference_issued_type != VoucherClaim::class){ return []; } $issued = $voucher->referenceIssued; if ($issued->claimable_type != AffiliatorItemCode::class){ return []; } $claimable = $issued->claimable; if ($claimable == null){ return []; } $affiliator = $claimable->affiliator; if ($claimable->affiliatorItem != null){ $affiliatorItem = $claimable->affiliatorItem; $itemReference = $affiliatorItem->item; $item_reference_ids = [$itemReference->id]; $gross = $this->findDetail($invoice, $item_reference_ids); $fee_nominal = $affiliatorItem->fee ? $affiliatorItem->fee : $gross * ($affiliator->fee_percentage/100); }else if ($claimable->codeable_type == Affiliatoritem::class){ $affiliatorItem = $claimable->codeable; $itemReference = $affiliatorItem->item; $item_reference_ids = [$itemReference->id]; $gross = $this->findDetail($invoice, $item_reference_ids); $fee_nominal = $affiliatorItem->fee ? $affiliatorItem->fee : $gross * ($affiliator->fee_percentage/100); }else if ($claimable->codeable_type == VoucherEvent::class){ $voucherEvent = $claimable->codeable; $item_reference_ids = $voucherEvent->items->pluck("id")->toArray(); $gross = $this->findDetail($invoice, $item_reference_ids); $fee_nominal = $gross * ($affiliator->fee_percentage/100); }else{ return []; } $incentive = new Incentive; $incentive->fill([ "employee_id" => 0, "person_id" => $affiliator->id, "person_type" => get_class($affiliator), "nominal" => $fee_nominal, "date" => Carbon::now(), "reference_id" => $voucher->id, "reference_type" => get_class($voucher), "status" => "OPEN" ]); $incentive->save(); return $item_reference_ids; } private function generateVoucher($nominal) { $code = "BLJ"; if ($nominal == 2000000) { $code = "2JT"; } else if ($nominal == 1000000) { $code = "1JT"; } else if ($nominal == 1500000) { $code = "1.5JT"; } else if ($nominal == 2500000) { $code = "2.5JT"; } else if ($nominal == 500000) { $code = "500RB"; } else if ($nominal == 750000) { $code = "750RB"; } else if ($nominal == 250000) { $code = "250RB"; } else if ($nominal == 100000) { $code = "100RB"; } else if ($nominal == 50000) { $code = "50RB"; } else if ($nominal == 3000000) { $code = "3JT"; } $voucher = null; $iter = 0; while ($voucher == null) { $new_code = strtoupper("EV/" . $code . "/" . date("Y") . "/" . bin2hex(openssl_random_pseudo_bytes(3))); $exists = Voucher::where("number", $new_code)->first(); $voucher = $exists ? null : $new_code; } return $voucher; } private function issueVoucher($invoice, $name, $vouchers, $qty, $exp) { $nominal = str_replace(".", "", $name); preg_match("/[0-9]++/", $nominal, $match); $nominal = (float) @$match[0]; $evoucher = true; if ($vouchers == "") { $arr = []; for ($i = 0; $i < $qty; $i++) { $arr[] = $this->generateVoucher($nominal); } $evoucher = true; } else { $arr = explode(",", $vouchers); $evoucher = false; } foreach ($arr as $voucher) { $voucher = trim($voucher); $item = Voucher::where("number", $voucher)->firstOrNew(); $item->customer_id = $invoice->customer_id; $item->evoucher = $evoucher; $item->number = $voucher; $item->nominal = $nominal; $item->issued_at = Carbon::now(); $item->expired_at = Carbon::now()->addMonth($exp); $item->reference_issued_id = $invoice->id; $item->reference_issued_type = get_class($invoice); $item->save(); } return implode(",", $arr); } public function update(PosInvoice $invoice, array $data) { if (!$this->isAdmin()) { $isCreatedToday = Carbon::now()->diffInMinutes(Carbon::parse($invoice->time)) <= (60 * 24); if (!$isCreatedToday) { return abort(403, 'hanya invoice yang belum 24 jam yang bisa di edit'); } } $data["vouchers"] = implode(",", $data["vouchers"]); $invoice->update($data); $ids = []; foreach ($data["details"] as $row) { $detail = $invoice->details()->find(@$row["id"]); if (!$detail){ $item = Items::find($row["item_id"]); $row["unit_price"] = (float) @$row["unit_price"]; $row["total"] = (float) @$row["total"]; $row["unit_cost"] = @$item ? $item->unit_cost : 0; $detail = $invoice->details()->create($row); }else{ $detail->update([ "qty" => $row["qty"], "serial_number" => $row["serial_number"], "total" => $row["total"], "unit_price" => $row["unit_price"], "discount" => $row["discount"], ]); $item = $detail->item; } $this->deactivateSerialNumber($invoice, @$row['serial_number']); $this->activateSerialNumber($invoice, @$row['serial_number'],[ "reference" => @$row["reference"], "description" => @$row["description"], "item_number" => @$row["item_number"], "variant_code" => @$row["variant_code"], "brand" => @$item->dimension->brand ]); $ids[] = $detail->id; } $invoice->details()->whereNotIn("id",$ids)->delete($ids); $ids = []; foreach ($data["payments"] as $row) { if ($row["amount"] <= 0) continue; $detail = $invoice->details()->find(@$row["id"]); if (!$detail){ $detail = $invoice->payments()->create($row); }else{ $detail->update($row); } $ids[] = $detail->id; } $invoice->payments()->whereNotIn("id",$ids)->delete(); return $invoice; } public function cancel(PosInvoice $item, $data) { $data["canceled_by"] = auth()->user()->id; $data["canceled_at"] = Carbon::now(); $item->fill($data); $item->save(); DB::table("customer_points")->where("reference_id", $item->id) ->where("reference_type", get_class($item))->delete(); DB::table("vouchers") ->where("reference_used_id", $item->id) ->where("reference_used_type", get_class($item)) ->update([ "used_at" => null ]); } // public function delete(PosInvoice $item) // { // $item->delete(); // } public function findBy($column, $value) { $item = PosInvoice::where($column, $value)->firstOrFail(); return $item; } public function isAdmin() { $admin = Role::where('name', 'ADMIN')->first(); $user = auth()->user(); return $user->role_id == $admin->id; } }