File: /var/www/ipsremont-demo/app/Services/Repair/RepairService.php
<?php
namespace App\Services\Repair;
use App\Http\Requests\Repair\CreateRequest;
use App\Http\Requests\Repair\IndexRequest;
use App\Http\Requests\Repair\UpdateRequest;
use App\Models\Currency;
use App\Models\Order;
use App\Models\Repair\Repair;
use App\Models\Service\Service;
use App\Repository\Repair\RepairRepository;
use App\Services\Status\StatusService;
use App\Traits\Stat;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Writer\Xls;
class RepairService
{
use Stat;
/** @var RepairRepository */
protected static RepairRepository $repository;
public static function setRepository()
{
self::$repository = new RepairRepository();
}
/**
* @return RepairRepository
*/
public static function getRepository()
{
self::setRepository();
return self::$repository;
}
/**
* Branch constructor.
*/
public function __construct()
{
self::setRepository();
}
/**
* @return array
*/
public static function getDisplayed()
{
return self::getRepository()::getDisplayed()->get()->pluck('name', 'id')->toArray();
}
/**
* @param $id
*
* @return mixed
*/
public static function getById($id)
{
return self::getRepository()::getAvailable()->findOrFail($id);
}
public static function getAvailableByStatus($id)
{
return self::getRepository()::getAvailableByStatus()->findOrFail($id);
}
/**
* @param IndexRequest $request
*
* @return mixed
*/
public function search(IndexRequest $request)
{
return self::getRepository()->search($request);
}
public function delete($id)
{
self::getRepository()->delete($id);
}
public static function getTwins($model)
{
$query = self::getRepository()::getAvailable()->where('id', '!=', $model->id)->where('service_id', $model->service_id);
$query->where(function ($query) use ($model) {
$query->orWhere('name', 'like', '%' . $model->name . '%')
->orWhere('email', 'like', '%' . $model->email . '%')
->orWhere('phone', 'like', '%' . $model->phone . '%')
->orWhere('serial_number', 'like', '%' . $model->serial_number . '%');
});
return $query->limit(10)->orderByDesc('created_at')->get();
}
public function save(CreateRequest $request)
{
$request->validate($request->rules());
$fields = $request->all();
return self::getRepository()->save($fields);
}
public function update(UpdateRequest $request)
{
$request->validate($request->rules());
$fields = $request->all();
$fields = $this->unsetEmptyParts($fields);
$repair = self::getRepository()::getAvailableByStatus()->findOrFail($request->id);
if ($fields['use_catalog_component']) {
$fields['device_sku'] = '';
$fields['device_name'] = '';
} else {
$fields['device_id'] = 0;
$fields['device_sku'] = $fields['custom_component_sku'];
$fields['device_name'] = $fields['custom_component_name'];
}
if (!empty($fields['custom_price'])) {
$fields['custom_price_rub'] = Currency::convertToRubles($fields['custom_price']);
}
if (!empty($fields['parts'])) {
foreach ($fields['parts'] as $key => $field) {
$fields['parts'][$key]['price_rub'] = Currency::convertToRubles($fields['parts'][$key]['price']);
}
}
if (!canServiceActions($repair->service_id)) {
return false;
}
self::getRepository()->update($repair, $fields);
}
public function unsetEmptyParts($fields)
{
foreach ($fields['parts'] as $key => $part) {
if (!$part['vendor_code'] || !$part['amount'] || !$part['price']) {
unset($fields['parts'][$key]);
}
}
if (empty($fields['parts'])) {
unset($fields['parts']);
}
return $fields;
}
public static function closeFromAct($id, $status, $category, $date, $description)
{
RepairRepository::closeFromAct($id, $status, $category, $date, $description);
}
public static function getDatesForReport(): array
{
return RepairRepository::getDatesForReport()->get()->pluck('close_date', 'close_date')->toArray();
}
/**
* @param string $startDate
* @param string $endDate
*
* @return Collection|Repair[]
*/
public static function getRepairsForReport(string $startDate, string $endDate)
{
return RepairRepository::getRepairsForReport($startDate, $endDate)->get();
}
public static function getRepairsForExport(bool $withErrors = false, array $branchIds = [])
{
return RepairRepository::getRepairsForExport($withErrors, $branchIds)->get();
}
public static function setStyle($sheet, $cell, $h_alignment, $font, $bold, $size, $borders)
{
$sheet->getStyle($cell)->applyFromArray([
'font' => [
'name' => $font,
'bold' => $bold,
'size' => $size,
],
'alignment' => [
'horizontal' => $h_alignment,
'vertical' => Alignment::VERTICAL_CENTER,
],
'borders' => $borders,
]);
}
public static function setCellValue($sheet, $cell, $text, $font, $size, $bold, $h_alignment = '', $borders = [])
{
$sheet->setCellValue($cell, $text);
self::setStyle($sheet, $cell, $h_alignment, $font, $bold, $size, $borders);
}
public static function getPartInfo($item): array
{
$name = (isset($item->part->name)) ? $item->part->name : '';
if (isset($item->part->external_id)) {
$external_id = $item->part->external_id;
} elseif ($item->part_external_id) {
$external_id = $item->part_external_id;
} else {
$external_id = '';
}
return [$name, $external_id, $item->amount, $item->price, $item->price * $item->amount];
}
protected static function generateXmlReport($repairs, ?Service $service, $from, $to, string $fileName)
{
$repairsByMonth = [];
foreach ($repairs as $repair) {
$month = date('Y-m-01', strtotime($repair->close_date));
$repairsByMonth[$month][] = $repair;
}
ksort($repairsByMonth);
foreach ($repairsByMonth as $repairs) {
$xml = '<?xml version="1.0" encoding="utf-8"?>' . PHP_EOL;
$xml .= '<Service>' . PHP_EOL;
$xml .= ' <start_date>' . date('01.m.Y', strtotime($from)) . '</start_date>' . PHP_EOL;
$xml .= ' <end_date>' . date('t.m.Y', strtotime($to)) . '</end_date>' . PHP_EOL;
$xml .= ' <external_id>' . ($service->external_id ?? '') . '</external_id>' . PHP_EOL;
$xml .= ' <service_name>' . ($service->name ?? '') . '</service_name>' . PHP_EOL;
$xml .= ' <inn>' . ($service->inn ?? '') . '</inn>' . PHP_EOL;
$xml .= ' <Repairs>' . PHP_EOL;
foreach ($repairs as $repair) {
/** @var Repair $repair */
$repair->report_name = str_replace('report_' . $service->id . '_', '', $fileName . '.xml');
$repair->save();
$orders = $repair->orders;
$hasOrders = !$orders->isEmpty();
$orders = $orders->filter(fn($order) => $order->repair == Order::REPAIR_WARRANTY)->all();
if ($hasOrders && empty($orders)) {
continue;
}
/** @var Repair $repair */
$repairXml = ' <repair>' . PHP_EOL;
$repairXml .= ' <id>' . $repair->getId() . '</id>' . PHP_EOL;
$repairXml .= ' <client_name>' . $repair->getName() . '</client_name>' . PHP_EOL;
$repairXml .= ' <address>' . $repair->getAddress() . '</address>' . PHP_EOL;
$repairXml .= ' <phone>' . $repair->getPhone() . '</phone>' . PHP_EOL;
$repairXml .= ' <device_external_id>' . $repair->device_external_id . '</device_external_id>' . PHP_EOL;
$repairXml .= ' <device_name>' . ($repair->use_catalog_component ? (isset($repair->device) ? $repair->device->getName() : '') : ($repair->device_sku . ', ' . $repair->device_name)) . '</device_name>' . PHP_EOL;
$repairXml .= ' <device_serial>' . $repair->getSerial() . '</device_serial>' . PHP_EOL;
$repairXml .= ' <additional_info>' . $repair->getAdditionalInfo() . '</additional_info>' . PHP_EOL;
$repairXml .= ' <work_price>' . $repair->getPriceFor1S() . '</work_price>' . PHP_EOL;
$repairXml .= ' <work_description>' . $repair->work_description . '</work_description>' . PHP_EOL;
$repairXml .= ' <error_code>' . $repair->error_code . '</error_code>' . PHP_EOL;
$repairXml .= ' <category_id>' . $repair->category_id . '</category_id>' . PHP_EOL;
$repairXml .= ' <sold_date>' . $repair->getSold() . '</sold_date>' . PHP_EOL;
$repairXml .= ' <repair_date>' . $repair->getRepairDate() . '</repair_date>' . PHP_EOL;
$repairXml .= ' <closed_date>' . $repair->getClosedDate() . '</closed_date>' . PHP_EOL;
$repairXml .= ' <Parts>' . PHP_EOL;
foreach ($repair->orders as $order) {
foreach ($order->order_items as $item) {
$partInfo = self::getPartInfo($item);
$partXml = ' <part>' . PHP_EOL;
$partXml .= ' <part_name>' . $partInfo[0] . '</part_name>' . PHP_EOL;
$partXml .= ' <code>' . $partInfo[1] . '</code>' . PHP_EOL;
$partXml .= ' <quantity>' . $partInfo[2] . '</quantity>' . PHP_EOL;
$partXml .= ' <part_price>0</part_price>' . PHP_EOL;
$partXml .= ' <total_price>0</total_price>' . PHP_EOL;
$partXml .= ' </part>' . PHP_EOL;
$repairXml .= $partXml;
}
}
foreach ($repair->custom_parts as $custom_part) {
$partXml = ' <part>' . PHP_EOL;
$partXml .= ' <part_name>Своя деталь</part_name>' . PHP_EOL;
$partXml .= ' <code>' . $custom_part->vendor_code . '</code>' . PHP_EOL;
$partXml .= ' <quantity>' . $custom_part->amount . '</quantity>' . PHP_EOL;
$partXml .= ' <part_price>' . $custom_part->getPriceFor1S() . '</part_price>' . PHP_EOL;
$partXml .= ' <total_price>' . $custom_part->getPriceFor1S($custom_part->amount) . '</total_price>' . PHP_EOL;
$partXml .= ' </part>' . PHP_EOL;
$repairXml .= $partXml;
}
$repairXml .= ' </Parts>' . PHP_EOL;
$repairXml .= ' </repair>' . PHP_EOL;
$xml .= $repairXml;
}
$xml .= ' </Repairs>' . PHP_EOL;
$xml .= '</Service>' . PHP_EOL;
$filePath = public_path('storage/reports/' . $fileName . '.xml');
$f = fopen($filePath, 'wb');
fwrite($f, $xml);
fclose($f);
File::chmod($filePath, 0777);
}
}
/**
* @param Collection|Repair[] $repairs
*
* @return string
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
public static function createReport($repairs)
{
$service = null;
foreach ($repairs as $repair) {
if (is_null($service) && isset($repair->service)) {
/** @var Service $service */
$service = $repair->service;
$serviceAddress = $service->getAddress();
$dataAboveTable = [$serviceAddress->city, $service->name, $serviceAddress->phone, $serviceAddress->email];
}
}
// Получаем крайние даты для формирования периода
$repairsByMonth = [];
foreach ($repairs as $repair) {
$month = date('Y-m-01', strtotime($repair->close_date));
$repairsByMonth[$month][] = $repair;
}
ksort($repairsByMonth);
$firstRepair = $repairsByMonth[array_key_first($repairsByMonth)][0];
$from = $firstRepair->closed_at;
$to = $firstRepair->closed_at;
foreach ($repairsByMonth as $repairByMonth) {
foreach ($repairByMonth as $repair) {
$from = $from->lt($repair->closed_at) ? $from : $repair->closed_at;
$to = $to->gt($repair->closed_at) ? $to : $repair->closed_at;
}
}
$fromFormattedDate = date('d.m.Y', strtotime($from));
$toFormattedDate = date('d.m.Y', strtotime($to));
$rows = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
$center = Alignment::HORIZONTAL_CENTER;
$right = Alignment::HORIZONTAL_RIGHT;
$borders = ['allBorders' => ['borderStyle' => Border::BORDER_THIN]];
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->getColumnDimension('A')->setWidth(5);
$sheet->getRowDimension(3)->setRowHeight(25);
$sheet->mergeCells('A3:C3');
$sheet->mergeCells('A4:D4');
$sheet->mergeCells('A5:D5');
$texts = ['Отчет', 'о выполненных работах'];
foreach ($texts as $key => $text) {
self::setCellValue($sheet, 'A' . (3 + $key), $text, 'Times New Roman', 16, true);
}
self::setCellValue($sheet, 'A5', 'за период с ' . $fromFormattedDate . ' по ' . $toFormattedDate, 'Times New Roman', 16, true);
$textAboveTable = ['Город:', 'Фирма:', 'Контактный тел.(c кодом города):', 'E-mail:'];
foreach ($textAboveTable as $key => $text) {
self::setCellValue($sheet, 'A' . (7 + $key), $text, 'Arial CYR', 10, false);
$sheet->mergeCells('A' . (7 + $key) . ':C' . (7 + $key));
if (isset($dataAboveTable)) {
self::setCellValue($sheet, 'D' . (7 + $key), $dataAboveTable[$key], 'Arial CYR', 10, false);
}
}
$sheet->mergeCells('A11:D11');
self::setCellValue($sheet, 'A11', 'За период времени с (по дату готовности аппарата):', 'Arial', 10, true);
$sheet->mergeCells('E11:G11');
self::setCellValue($sheet, 'E11', 'c ' . $fromFormattedDate . ' по ' . $toFormattedDate, 'Arial', 10, true);
$currencyShortName = $service->branch->region->currency->short_name;
$headerTableTexts = [
'№ п/п',
'№ наряд-заказа',
"ФИО \nзакзчика",
"Адрес \nзаказчика,\n телефон",
"Код \nаппарата",
"Наименование \nаппарата",
"Серийный \nномер",
"Описание \nдефекта",
"Выполненные \nработы",
"Код \nремонта",
"Категория \nремонта",
"Дата \nпродажи",
"Дата \nприемки",
"Дата \nвыдачи",
"Наименование \nзапчасти",
"Партномер \nзапчасти \n(код)",
"Количество \nдеталей, шт.",
"Цена \nдетали, " . $currencyShortName . '.',
"Стоимость \nдеталей, " . $currencyShortName . '.',
"Стоимость \nремонта, " . $currencyShortName . '.',
"ИТОГО \nстоимость, " . $currencyShortName . '.',
];
$change_width = ['2' => '30', '3' => '20', '5' => '15', '7' => '30', '8' => '20', '14' => '30', '16' => '20'];
foreach ($headerTableTexts as $key => $text) {
if ($key) {
if (array_key_exists($key, $change_width)) {
$sheet->getColumnDimension($rows[$key])->setWidth($change_width[$key]);
}
$sheet->getRowDimension(13)->setRowHeight(30);
$sheet->getStyle($rows[$key] . '13')->getAlignment()->setWrapText(true);
}
self::setCellValue($sheet, $rows[$key] . '13', $text, 'Arial', 8, true, $center, $borders);
self::setCellValue($sheet, $rows[$key] . '14', $key + 1, 'Arial', 8, true, $center, $borders);
}
$col = 15;
$global_repair_sum = 0;
$globalPartsSum = 0;
foreach ($repairs as $key => $repair) {
/** @var Repair $repair */
/** @var Collection|Order[] $orders */
$orders = $repair->orders->filter(fn($order) => $order->repair == Order::REPAIR_WARRANTY)->all();
$firstCol = $col;
$firstOrder = $orders[0] ?? null;
$partInfo = ['', '', '', '', ''];
if (isset($firstOrder->order_items[0])) {
$partInfo = self::getPartInfo($firstOrder->order_items[0]);
}
$address = $repair->getAddress();
if (!empty($address)) {
$address .= ', ';
}
$address .= $repair->getPhone();
$data = [
$key + 1,
$repair->getNumber(),
html_entity_decode($repair->getName(), ENT_QUOTES, 'UTF-8'),
$address,
$repair->device_external_id,
$repair->use_catalog_component ? $repair->getDevice()->getName() : ($repair->device_sku . ', ' . $repair->device_name),
$repair->getSerial() ?: 'Отсутствует',
$repair->getAdditionalInfo(),
$repair->work_description,
$repair->getErrorCode(),
$repair->category_id,
$repair->getSold(),
$repair->getRepairDate(),
$repair->getClosedDate(),
$partInfo[0],
$partInfo[1],
$partInfo[2],
0,
0,
$repair->getCustomOrDefaultPrice(),
'',
];
foreach ($data as $i => $text) {
$align = ($i > 16) ? $right : $center;
$text = str_replace('🪫', '', $text);
self::setCellValue($sheet, $rows[$i] . $col, $text, 'Times New Roman', 10, false, $align, $borders);
}
$col++;
$sum = 0;
$firstLoop = true;
foreach ($orders as $order) {
// Цена заявки не должна учитываться
// $sum += $order->getPrice();
// $globalPartsSum += $order->getPrice();
foreach ($order->order_items as $item) {
if ($firstLoop) {
$firstLoop = false;
continue;
}
$partInfo = self::getPartInfo($item);
$data = [];
$data = array_pad($data, 14, '');
$data[] = $partInfo[0];
$data[] = $partInfo[1];
$data[] = $partInfo[2];
$data = array_pad($data, 19, 0);
$data = array_pad($data, 21, '');
foreach ($data as $i => $text) {
$align = ($i > 16) ? $right : $center;
self::setCellValue($sheet, $rows[$i] . $col, $text, 'Times New Roman', 10, false, $align, $borders);
}
$col++;
}
}
foreach ($repair->custom_parts as $customPart) {
$sum += $customPart->price * $customPart->amount;
$globalPartsSum += $customPart->price * $customPart->amount;
$data = [];
$data = array_pad($data, 14, '');
$data[] = 'Своя деталь';
$data[] = $customPart->vendor_code;
$data[] = $customPart->amount;
$data[] = $customPart->price;
$data[] = $customPart->price * $customPart->amount;
$data = array_pad($data, 21, '');
foreach ($data as $i => $text) {
$align = ($i > 16) ? $right : $center;
self::setCellValue($sheet, $rows[$i] . $col, $text, 'Times New Roman', 10, false, $align, $borders);
}
$col++;
}
$global_repair_sum += $repair->getCustomOrDefaultPrice();
$sum += $repair->getCustomOrDefaultPrice();
self::setCellValue($sheet, $rows[count($headerTableTexts) - 1] . $firstCol, $sum, 'Times New Roman', 10, false, $right, $borders);
}
self::setCellValue($sheet, 'R' . $col, 'Итого', 'Times New Roman', 10, true, '', $borders);
self::setCellValue($sheet, 'S' . $col, $globalPartsSum, 'Times New Roman', 10, true, $right, $borders);
self::setCellValue($sheet, 'T' . $col, $global_repair_sum, 'Times New Roman', 10, true, $right, $borders);
self::setCellValue($sheet, 'U' . $col, $globalPartsSum + $global_repair_sum, 'Times New Roman', 10, true, $right, $borders);
$col += 2;
self::setCellValue($sheet, 'D' . $col, 'Дата заполнения: ', 'Arial', 10, false);
self::setCellValue($sheet, 'E' . $col, date('d.m.Y'), 'Arial', 10, false, $center);
$col += 3;
self::setCellValue($sheet, 'D' . $col, '__________/ /', 'Arial', 10, false);
File::ensureDirectoryExists(public_path('storage/reports'), 0777);
File::chmod(public_path('storage/reports'), 0777);
$fileName = 'report_' . $service->id . '_' . date('Y-m-d', strtotime($from)) . '_' . date('Y-m-d', strtotime($to));
$writer = new Xls($spreadsheet);
$file_path = public_path('storage/reports/' . $fileName . '.xls');
$writer->save($file_path);
File::chmod($file_path, 0777);
self::generateXmlReport($repairs, $service, $from, $to, $fileName);
return $file_path;
}
public function getLast()
{
return self::$repository->getLast()->get();
}
/** Считает статистику нарядов */
public function getStats(string $start, string $end, ?array $serviceIds = []): array
{
$statuses = StatusService::getRepairAll();
/** @var Builder $query */
$query = self::getRepository()::getAvailable();
$query->whereBetween('created_at', [$start . ' 00:00:00', $end . ' 23:59:59']);
if (!empty($serviceIds)) {
$query->whereIn('service_id', $serviceIds);
}
return $this->countBySatuses($statuses, $query);
}
}