building-sign/app/service/Pay.php

226 lines
9.9 KiB
PHP
Raw Normal View History

2023-01-09 08:41:41 +00:00
<?php
namespace app\service;
use app\model\ClockLog;
use app\model\Log;
use app\model\OvertimeLog;
use app\model\PayLog;
use app\model\PayMonthLog;
use think\facade\Db;
use think\Model;
/**
* 薪资计算
*
* Class Pay
* @package app\service
*/
class Pay
{
// 基本逻辑 工人工资=基本工资+加班工资 一个月的工资=有效打卡的天数*基本工资+加班工资(=天工资/6*加班小时)
//基本工资在员工入职就要填入
//基本工资按天计算,打卡确认一天算一天
//加班工资按6小时为一天天工资÷6小时=每小时工资
/*
基本工资按天计算,打卡确认一天算一天,可能会出现的漏洞(本期暂不考虑)
晚上加班如23:10打上班卡第二天03:10打下班卡这样就会认为上班2天。
若正常一天,必须至少要有上班和下班两次打卡的话,上述则两天都不算上班
*/
// $sql = "
// insert into bee_pay_log(`account_id`, `worksite_id`,`time`, `year`, `month`, `day`)
// select ".$item['account_id'].",".$item['worksite_id'].",".$item['day'].",".$year.",".$month.",".$day." from DUAL
// where not exists (select `account_id`, `worksite_id`,`time` from bee_pay_log where `account_id` = ".$item['account_id']." AND `worksite_id` = ".$item['worksite_id']." AND `time` = ".$item['day'].")
//";
// Db::execute($sql);
// 生成工人每个工地月薪资汇总
/**
* 汇总指定日期间的工资
* 汇总到pay_log表
*
* @param int $begin 开始时间 为0则不限制 格式为Ymd 如20221103
* @param int $end 结束时间 为0则不限制 格式为Ymd 如20221103
*/
public static function statistic(int $begin = 0, int $end = 0)
{
set_time_limit(0);
$where = [];
if ($begin) {
$where[] = ['l.day', '>=', $begin];
}
if ($end) {
$where[] = ['l.day', '<=', $end];
}
// 打卡记录表 每300条处理一次 打卡记录表中同一个indexs只计算一次
ClockLog::alias('l')
->leftJoin('account a', 'a.id = l.account_id')
->leftJoin('pay_log pl', 'pl.indexs = l.indexs')
->where('l.is_statistic', ClockLog::COMMON_OFF)
->where('l.status', ClockLog::COMMON_ON)
->where('a.pay', '>', 0)
->where('l.handle_count', '<=', 10) //查询处理次数10次以下
->where($where)
->group('l.indexs')
->fieldRaw('count(l.id),l.id,l.account_id,l.worksite_id,l.created_at,l.day,l.indexs,a.pay,pl.id as pay_log_id,pl.base_amount_count')
->chunk(300, function ($items) {
$update = [];
$indexsList = [];
$ids = $items->column('id');
\think\facade\Log::write($ids);
// 每查询一次就增加一次handle_count 避免未处理成功后 死循环一直查询
(new ClockLog)->whereIn('id', $ids)->save([
'handle_count' => Db::raw('`handle_count` + 1')
]);
foreach ($items as $item) {
if ($item['pay_log_id'] && $item['base_amount_count'] == 0) {
$update[] = [
'id' => $item['pay_log_id'],
'amount' => Db::raw('`amount` + '.$item['pay']),
'base_amount' => Db::raw('`base_amount` + '.$item['pay']),
'remarks' => Db::raw('CONCAT_WS(char(10), `remarks`,"基本工资入账 '.$item['pay'].'元")'),
'base_amount_count' => 1,
];
$indexsList[] = $item['indexs'];
}
}
if (!empty($update)) {
// \think\facade\Log::write($update);
// 启动事务
Db::startTrans();
try {
(new PayLog())->saveAll($update);
ClockLog::whereIn('indexs', $indexsList)->update(['is_statistic' => ClockLog::COMMON_ON]);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
\think\facade\Log::error('批量统计基本工资失败'.$e->getMessage().' file:'.$e->getFile().' line:'.$e->getLine());
}
}
}, 'pl.id', 'desc');
// 加班记录表 每300条处理一次
OvertimeLog::alias('l')
->leftJoin('account a', 'a.id = l.account_id')
->leftJoin('pay_log pl', 'pl.indexs = l.indexs')
->where('l.is_statistic', ClockLog::COMMON_OFF)
->where('l.status', ClockLog::COMMON_ON)
->where('a.pay', '>', 0)
->where('l.handle_count', '<=', 10) //查询处理次数10次以下
->where($where)
->fieldRaw('l.id,l.account_id,l.worksite_id,l.created_at,l.`day`,l.time,l.indexs,a.pay,pl.id as pay_log_id,pl.overtime_amount_count')
->chunk(300, function ($items) {
$update = [];
$ids = [];
$handleIds = $items->column('id');
// 每查询一次就增加一次handle_count 避免未处理成功后 死循环一直查询
(new OvertimeLog)->whereIn('id', $handleIds)->save([
'handle_count' => Db::raw('`handle_count` + 1')
]);
foreach ($items as $item) {
if ($item['pay_log_id']) {
// 加班工资=基本工资/6小时 * 加班小时
$overtimeAmount = round(Math::mul(Math::div($item['pay'], 6), $item['time']), 2);
$update[] = [
'id' => $item['pay_log_id'],
'amount' => Db::raw('`amount` + '.$overtimeAmount),
'overtime_amount' => Db::raw('`overtime_amount` + '.$overtimeAmount),
'remarks' => Db::raw('CONCAT_WS(char(10), `remarks`,"加班工资入账 '.$overtimeAmount.'元")'),
'overtime_amount_count' => Db::raw('`overtime_amount_count` + 1'),
];
$ids[] = $item['id'];
}
}
// \think\facade\Log::write($update);
if (!empty($update)) {
Db::startTrans();
try {
(new PayLog())->saveAll($update);
OvertimeLog::whereIn('id', $ids)->update(['is_statistic' => OvertimeLog::COMMON_ON]);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
\think\facade\Log::error('批量统计加班工资失败'.$e->getMessage().' file:'.$e->getFile().' line:'.$e->getLine());
}
}
}, 'l.id', 'desc');
}
/**
* 生成月份工资记录
*
* @param int $time 生成时间 按月份 格式为年月 如202209 date('Ym')
* @return bool
*/
public static function generateMonthLog(int $time = 0): bool
{
if ($time == 0) {
// 默认生成上个月
$time = date('Ym', strtotime('last month'));
}
$timestamp = strtotime($time.'01');
$year = date('Y', $timestamp);
$month = date('m', $timestamp);
$where = [
['year', '=', $year],
['month', '=', $month],
];
// 每次统计 都讲之前的结果清理,插入新计算结果
$insert = [];
// 每日工资记录(基本工资和加班工资)
PayLog::where($where)
->chunk(300, function ($items) use (&$insert, $time, $year, $month) {
foreach ($items as $item) {
$indexs = $item['account_id'].'-'.$item['worksite_id'].'-'.$time;
if (!isset($insert[$indexs])) {
$insert[$indexs] = [
'account_id' => $item['account_id'],
'worksite_id' => $item['worksite_id'],
'indexs' => $indexs,
'time' => $time,
'year' => $year,
'month' => $month,
'created_at' => date('Y-m-d H:i:s'),
'amount' => $item['amount'],
'base_amount' => $item['base_amount'],
'overtime_amount' => $item['overtime_amount'],
];
} else {
$insert[$indexs]['amount'] = $insert[$indexs]['amount'] + $item['amount'];
$insert[$indexs]['base_amount'] = $insert[$indexs]['base_amount'] + $item['base_amount'];
$insert[$indexs]['overtime_amount'] = $insert[$indexs]['overtime_amount'] + $item['overtime_amount'];
}
}
});
if (!empty($insert)) {
// \think\facade\Log::write($insert);
// 启动事务
Db::startTrans();
try {
PayMonthLog::where('time', $time)->delete();
(new PayMonthLog())->insertAll($insert);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
\think\facade\Log::error('统计['.$time.']工资失败'.$e->getMessage().' file:'.$e->getFile().' line:'.$e->getLine());
}
}
return true;
}
}