You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

310 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Models;
use App\Customer;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class OrderItems extends SoftDeletesModel
{
protected $table = "order_items";
const PREV_MONTH = "往月服务单";
protected $appends = ["paid_status"];
public function getPaidStatusAttribute()
{
if ($this->total == 0) {
return "未服务";
}
if ($this->paid_at) {
$next_month = date("m", strtotime("+1 month", strtotime(date("Y-m", strtotime($this->paid_at)))));
return "已扣款,{$next_month}月结工资";
} else {
return "未扣款";
}
}
public function order()
{
return $this->belongsTo(Orders::class);
}
public function siblings()
{
return $this->hasManyThrough(OrderItems::class, Orders::class, "id", "order_id", "order_id", "id");
}
public function customer()
{
return $this->hasOneThrough(Customer::class, Orders::class, "id", "id", "order_id", "customer_id");
}
public function product()
{
return $this->hasOneThrough(Product::class, ProductItems::class, "id", "id", "product_item_id", "product_id")
->withoutGlobalScope(\App\Scopes\AdminProjectScope::class)
->withTrashedParents();
}
public function productItem()
{
return $this->belongsTo(ProductItems::class);
}
public function productParamedicLevel()
{
return $this->belongsTo(ProductParamedicLevel::class);
}
public function paramedicLevel()
{
return $this->hasOneThrough(ParamedicLevel::class, ProductParamedicLevel::class, "id", "id", "product_paramedic_level_id", "paramedic_level_id");
}
public function bed()
{
return $this->belongsTo(Bed::class)->withTrashed();
}
public function room()
{
return $this->hasOneThrough(Room::class, Bed::class, "id", "id", "bed_id", "room_id")->withTrashed();
}
public function building()
{
return $this->hasOneThrough(Building::class, Bed::class, "id", "id", "bed_id", "building_id")->withTrashed();
}
public function area()
{
return $this->hasOneThrough(Area::class, Bed::class, "id", "id", "bed_id", "area_id")->withTrashed();
}
public function paramedic()
{
return $this->belongsTo(Paramedic::class)->withTrashed();
}
public function createItem($order_id, $service_date)
{
$record = (new OrderItems())->where([
"order_id" => $order_id,
"service_date" => $service_date
])->first();
if ($record) {
return $record;
}
$order = (new Orders())->find($order_id);
$patient_quantity = (new Orders())
->where("status", Orders::STATUS_ONGOING)
->where("paramedic_id", $order->paramedic_id)
->count();
$order_item = [
"order_id" => $order->id,
"product_item_id" => $order->product_item_id,
"product_paramedic_level_id" => $order->product_paramedic_level_id,
"bed_id" => $order->bed_id,
"paramedic_id" => $order->paramedic_id,
"service_date" => $service_date,
"patient_quantity" => $patient_quantity,
"total" => $order->price,
"factors" => $order->factors
];
return $this->create($order_item);
}
public function updateTodayItem($order_id)
{
$service_date = date("Y-m-d");
$record = (new OrderItems())->where([
"order_id" => $order_id,
"service_date" => $service_date
])->first();
if (!$record) {
return [
"status" => 0,
"msg" => "没找到当日子订单"
];
}
if ($record->paid_at) {
return [
"status" => 0,
"msg" => "当日子订单已扣款,请移步至子订单修改"
];
}
$order = (new Orders())->find($order_id);
$patient_quantity = (new Orders())
->where("status", Orders::STATUS_ONGOING)
->where("paramedic_id", $order->paramedic_id)
->count();
$update = [
"product_item_id" => $order->product_item_id,
"product_paramedic_level_id" => $order->product_paramedic_level_id,
"bed_id" => $order->bed_id,
"paramedic_id" => $order->paramedic_id,
"patient_quantity" => $patient_quantity,
"total" => $order->price,
"factors" => $order->factors
];
$record->update($update);
return [
"status" => 1,
"msg" => "当日子订单同步成功"
];
}
public function calculateFee()
{
$factors = json_decode($this->factors);
$factor_texts = [];
if ($factors) {
foreach ($factors as $factor_item) {
if ($factor_item->used_for_fee) {
$add_text = "(管理费{$factor_item->fee_percent}%+{$factor_item->fee})";
} else {
$add_text = "";
}
$factor_texts[] = "{$factor_item->factor_name}{$factor_item->factor_item_name}{$add_text}";
}
}
$this->factor_texts = $factor_texts;
// 标志变量:是否成功计算了管理费
$feeCalculated = false;
if (!$this->product) {
// 没有 product 时设置默认值(不扣除管理费)
$this->paramedic_total = $this->total ?? 0;
$this->fee = 0;
return $this;
}
switch ($this->product->fee_type) {
case "factor":
$factor = collect($factors)->filter(function ($item) {
return $item->used_for_fee;
})->first();
if (!$factor) {
//todo:检查factors为什么没有进去、缺失的factors已经手工补录进去了
$factorModel = (new Factor())
->with("factorItems")
->where("product_id", $this->product->id)
->where("used_for_fee", 1)
->first();
if ($factorModel && $factorModel->factorItems) {
$factor = $factorModel->factorItems->first();
}
}
if (!$factor) {
// 找不到因子时设置默认值(不扣除管理费)
$this->paramedic_total = $this->total ?? 0;
$this->fee = 0;
return $this;
}
//todo:考虑不同项目的情况,目前是全部统一
if (!$this->order) {
// 没有 order 时设置默认值(不扣除管理费)
$this->paramedic_total = $this->total ?? 0;
$this->fee = 0;
return $this;
}
$holidays = cache("holidays_" . $this->order->project_id);
if (!$holidays) {
$holidays = Holiday::where("project_id", $this->order->project_id)->get()->keyBy("date")->toArray();
cache(['holidays' . $this->order->project_id => $holidays], now()->addSeconds(90)); //只保存较短时间,省却了更新节假日时的缓存更新机制
}
if (in_array($this->service_date, array_keys($holidays))) {
$fee = $factor->fee_percent * ($this->total / $holidays[$this->service_date]["price_ratio"]) / 100 + $factor->fee - $this->fee_free;
} else {
$fee = $factor->fee_percent * $this->total / 100 + $factor->fee - $this->fee_free;
}
$paramedic_total = $this->total - $fee;
$this->paramedic_total = $paramedic_total;
$this->fee = $fee;
$feeCalculated = true;
break;
}
// 兜底逻辑:如果管理费没有被计算(通常是 fee_type 不是 "factor" 的情况),使用默认值
if (!$feeCalculated) {
$this->paramedic_total = $this->total ?? 0;
$this->fee = 0;
}
return $this;
}
public function autoCheckout()
{
$threshold = 50;
$last_id = cache("last_auto_checkout_order_item_id", 0);
Log::channel("daily_auto_checkout")->info("Last id:" . $last_id);
// 获取所有状态为1的项目
$projects = Project::where("status", 1)->pluck("id")->toArray();
$unpaid_order_items = (new OrderItems())
->whereHas("order", function ($query) use ($projects) {
$query->where("status", Orders::STATUS_ONGOING)
->whereIn("project_id", $projects);
})
->where("total", ">", 0)
->whereNull("paid_at")
->where("id", ">", $last_id)
->with("customer");
$unpaid_order_items = $unpaid_order_items
->orderBy("id")
->limit($threshold)
->get();
if (!$unpaid_order_items->count()) {
cache(['last_auto_checkout_order_item_id' => null], now()->addSeconds(90));
return;
}
Log::channel("daily_auto_checkout")->info("From " . $unpaid_order_items->first()->id . " to " . $unpaid_order_items->last()->id);
foreach ($unpaid_order_items as $item) {
$customer = $item->customer;
if ($customer->balance < $item->total || $item->total == 0) {
continue;
}
$item->update(["paid_at" => date("Y-m-d H:i:s")]);
$balance = $customer->balance - $item->total;
(new Balance())->create([
"customer_id" => $customer->id,
"order_id" => $item->order_id,
"belongs_type" => get_class($item),
"belongs_id" => $item->id,
"money" => -$item->total,
"balance" => $balance
]);
$customer->update(["balance" => $balance]);
Log::channel("daily_auto_checkout")->info($item->id . "扣款成功");
}
cache(['last_auto_checkout_order_item_id' => $unpaid_order_items->last()->id], now()->addSeconds(90));
}
public function balance()
{
return $this->hasOne(Balance::class, "belongs_id")->where("belongs_type", self::class);
}
}