|
|
@@ -8,15 +8,17 @@ use App\Models\Order;
|
|
|
use App\Models\ProductSKU;
|
|
|
use Illuminate\Support\Carbon;
|
|
|
use Illuminate\Validation\ValidationException;
|
|
|
-use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
|
|
-use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
|
-use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
|
|
-use PhpOffice\PhpSpreadsheet\Style\Border;
|
|
|
-use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
|
|
+use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
|
+use PhpOffice\PhpSpreadsheet\Shared\Date;
|
|
|
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
|
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
|
|
|
|
class ContractorSpecificationService
|
|
|
{
|
|
|
+ private const TEMPLATE = 'templates/Specification.xlsx';
|
|
|
+ private const ITEM_START_ROW = 15;
|
|
|
+ private const TEMPLATE_ITEM_ROWS = 6;
|
|
|
+
|
|
|
public function generate(Order $order, Contractor $contractor, array $data): string
|
|
|
{
|
|
|
$skus = ProductSKU::query()
|
|
|
@@ -33,25 +35,25 @@ class ContractorSpecificationService
|
|
|
|
|
|
$items = $this->buildItems($skus, $contractor);
|
|
|
|
|
|
- $spreadsheet = new Spreadsheet();
|
|
|
+ $spreadsheet = IOFactory::load(base_path(self::TEMPLATE));
|
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
|
- $sheet->setTitle('Спецификация');
|
|
|
|
|
|
$specDate = Carbon::parse($data['specification_date']);
|
|
|
$workStart = !empty($data['work_start_date']) ? Carbon::parse($data['work_start_date']) : null;
|
|
|
$workEnd = !empty($data['work_end_date']) ? Carbon::parse($data['work_end_date']) : null;
|
|
|
|
|
|
- $sheet->setCellValue('A2', 'Договор №' . $contractor->contract_number . ' от ' . $contractor->contract_date->format('d.m.Y') . ' г.');
|
|
|
- $sheet->setCellValue('A5', 'Спецификация №' . $data['specification_number'] . ' от ' . $specDate->format('d.m.Y') . ' г.');
|
|
|
- $sheet->setCellValue('A9', $contractor->contract_header);
|
|
|
- $sheet->setCellValue('A12', 'г. Москва, ' . $order->common_name);
|
|
|
+ $itemCount = count($items);
|
|
|
+ $this->prepareItemRows($sheet, $itemCount);
|
|
|
|
|
|
- $headers = ['№ п/п', 'Наименование МАФ', 'Цена', 'Ед. изм', 'Кол-во', 'Стоимость'];
|
|
|
- foreach ($headers as $index => $header) {
|
|
|
- $sheet->setCellValue(Coordinate::stringFromColumnIndex($index + 1) . '14', $header);
|
|
|
- }
|
|
|
+ $sheet->setCellValue('A2', 'к Договору подряда №' . $contractor->contract_number . ' от ' . $contractor->contract_date->format('d.m.Y') . ' г.');
|
|
|
+ $sheet->setCellValue('A5', 'Спецификация №');
|
|
|
+ $sheet->setCellValue('D5', $data['specification_number']);
|
|
|
+ $sheet->setCellValue('A7', 'г.Москва');
|
|
|
+ $sheet->setCellValue('E7', Date::PHPToExcel($specDate));
|
|
|
+ $sheet->setCellValue('A9', $contractor->contract_header);
|
|
|
+ $sheet->setCellValue('A12', 'г.Москва, ' . $order->common_name);
|
|
|
|
|
|
- $row = 15;
|
|
|
+ $row = self::ITEM_START_ROW;
|
|
|
$total = 0.0;
|
|
|
foreach ($items as $index => $item) {
|
|
|
$sum = $item['price'] * $item['quantity'];
|
|
|
@@ -59,14 +61,14 @@ class ContractorSpecificationService
|
|
|
|
|
|
$sheet->setCellValue('A' . $row, $index + 1);
|
|
|
$sheet->setCellValue('B' . $row, $item['name']);
|
|
|
- $sheet->setCellValue('C' . $row, $item['price']);
|
|
|
- $sheet->setCellValue('D' . $row, $item['unit']);
|
|
|
- $sheet->setCellValue('E' . $row, $item['quantity']);
|
|
|
+ $sheet->setCellValue('C' . $row, $item['unit']);
|
|
|
+ $sheet->setCellValue('D' . $row, $item['quantity']);
|
|
|
+ $sheet->setCellValue('E' . $row, $item['price']);
|
|
|
$sheet->setCellValue('F' . $row, $sum);
|
|
|
$row++;
|
|
|
}
|
|
|
|
|
|
- $summaryRow = max(21, $row + 1);
|
|
|
+ $summaryRow = self::ITEM_START_ROW + $itemCount;
|
|
|
$vatRow = $summaryRow + 1;
|
|
|
$totalWordsRow = $summaryRow + 2;
|
|
|
$vatTextRow = $summaryRow + 3;
|
|
|
@@ -74,54 +76,86 @@ class ContractorSpecificationService
|
|
|
$workEndRow = $summaryRow + 5;
|
|
|
$contractRow = $summaryRow + 6;
|
|
|
$legalNameRow = $summaryRow + 9;
|
|
|
- $signerTitleRow = $summaryRow + 10;
|
|
|
+ $signerTitleRow = $summaryRow + 11;
|
|
|
$directorRow = $summaryRow + 13;
|
|
|
|
|
|
$vat = $this->calculateVat($total, $contractor->tax_rate);
|
|
|
|
|
|
- $sheet->setCellValue('E' . $summaryRow, 'Итого');
|
|
|
+ $sheet->setCellValue('E' . $summaryRow, 'Итого:');
|
|
|
$sheet->setCellValue('F' . $summaryRow, $total);
|
|
|
|
|
|
- if ($vat !== null) {
|
|
|
- $sheet->setCellValue('E' . $vatRow, 'НДС');
|
|
|
+ if ($vat === null) {
|
|
|
+ $sheet->removeRow($vatRow);
|
|
|
+ $totalWordsRow--;
|
|
|
+ $vatTextRow--;
|
|
|
+ $workStartRow--;
|
|
|
+ $workEndRow--;
|
|
|
+ $contractRow--;
|
|
|
+ $legalNameRow--;
|
|
|
+ $signerTitleRow--;
|
|
|
+ $directorRow--;
|
|
|
+ } else {
|
|
|
+ $sheet->setCellValue('E' . $vatRow, 'В т.ч. НДС ' . $contractor->tax_rate . '%');
|
|
|
$sheet->setCellValue('F' . $vatRow, $vat);
|
|
|
}
|
|
|
|
|
|
- $sheet->setCellValue('A' . $totalWordsRow, $this->formatAmountWithWords($total));
|
|
|
- $sheet->mergeCells('A' . $totalWordsRow . ':F' . $totalWordsRow);
|
|
|
+ $sheet->setCellValue('A' . $totalWordsRow, 'Итого:');
|
|
|
+ $sheet->setCellValue('B' . $totalWordsRow, $this->formatAmountWithWords($total));
|
|
|
|
|
|
if ($vat === null) {
|
|
|
$sheet->setCellValue('A' . $vatTextRow, 'Без НДС');
|
|
|
} else {
|
|
|
- $sheet->setCellValue('A' . $vatTextRow, 'В т.ч. НДС ' . $contractor->tax_rate . '% ' . number_format($vat, 2, ',', ' ') . ' руб.');
|
|
|
+ $sheet->setCellValue('A' . $vatTextRow, 'В т.ч. НДС ' . $contractor->tax_rate . '% ' . number_format($vat, 2, ',', ' ') . ' р.');
|
|
|
}
|
|
|
|
|
|
- $sheet->setCellValue('A' . $workStartRow, 'Начало работ: ' . ($workStart?->format('d.m.Y') ?? ''));
|
|
|
- $sheet->setCellValue('A' . $workEndRow, 'Окончание работ: ' . ($workEnd?->format('d.m.Y') ?? ''));
|
|
|
- $sheet->setCellValue('A' . $contractRow, 'Договор №' . $contractor->contract_number . ' от ' . $contractor->contract_date->format('d.m.Y') . ' г.');
|
|
|
+ $sheet->setCellValue('A' . $workStartRow, '2. Срок начала выполнения работ Подрядчиком – не позднее ');
|
|
|
+ $sheet->setCellValue('E' . $workStartRow, $workStart ? Date::PHPToExcel($workStart) : '');
|
|
|
+ $sheet->setCellValue('A' . $workEndRow, '3. Срок окончания выполнения работ Подрядчиком – не позднее ');
|
|
|
+ $sheet->setCellValue('E' . $workEndRow, $workEnd ? Date::PHPToExcel($workEnd) : '');
|
|
|
+ $sheet->setCellValue(
|
|
|
+ 'A' . $contractRow,
|
|
|
+ '4. Настоящая Спецификация служит основанием для производства взаимных платежей и расчетов между Подрядчиком и Заказчиком.' . "\n"
|
|
|
+ . '5. Настоящая Спецификация является неотъемлемой частью Договора №' . $contractor->contract_number
|
|
|
+ . ' от ' . $contractor->contract_date->format('d.m.Y')
|
|
|
+ . ' г., составлена в 2 (двух) экземплярах, имеющих равную юридическую силу, по одному экземпляру для каждой из Сторон'
|
|
|
+ );
|
|
|
$sheet->setCellValue('A' . $legalNameRow, $contractor->legal_name);
|
|
|
$sheet->setCellValue('A' . $signerTitleRow, $contractor->signer_title);
|
|
|
$sheet->setCellValue('A' . $directorRow, $contractor->director_name);
|
|
|
|
|
|
- $lastRow = $directorRow;
|
|
|
- $sheet->getStyle('A14:F' . max(14, $row - 1))->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN);
|
|
|
- $sheet->getStyle('A14:F14')->getFont()->setBold(true);
|
|
|
- $sheet->getStyle('A14:F14')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
|
|
- $sheet->getStyle('C15:C' . $lastRow)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_NUMBER_00);
|
|
|
- $sheet->getStyle('F15:F' . $lastRow)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_NUMBER_00);
|
|
|
- $sheet->getStyle('A9:A12')->getAlignment()->setWrapText(true);
|
|
|
-
|
|
|
- foreach (['A' => 10, 'B' => 50, 'C' => 14, 'D' => 12, 'E' => 12, 'F' => 16] as $column => $width) {
|
|
|
- $sheet->getColumnDimension($column)->setWidth($width);
|
|
|
- }
|
|
|
+ $sheet->removeColumn('G');
|
|
|
+ $sheet->getAutoFilter()->setRange('A14:F' . $summaryRow);
|
|
|
+ $sheet->getPageSetup()->setPrintArea('A1:F' . ($directorRow + 2));
|
|
|
|
|
|
$safeNumber = preg_replace('/[^0-9A-Za-zА-Яа-я_-]+/u', '_', (string) $data['specification_number']);
|
|
|
$path = storage_path('app/specification-' . $order->id . '-' . $contractor->id . '-' . $safeNumber . '.xlsx');
|
|
|
- (new Xlsx($spreadsheet))->save($path);
|
|
|
+ $writer = new Xlsx($spreadsheet);
|
|
|
+ $writer->setPreCalculateFormulas(false);
|
|
|
+ $writer->save($path);
|
|
|
|
|
|
return $path;
|
|
|
}
|
|
|
|
|
|
+ private function prepareItemRows(Worksheet $sheet, int $itemCount): void
|
|
|
+ {
|
|
|
+ if ($itemCount > self::TEMPLATE_ITEM_ROWS) {
|
|
|
+ $extraRows = $itemCount - self::TEMPLATE_ITEM_ROWS;
|
|
|
+ $insertBefore = self::ITEM_START_ROW + self::TEMPLATE_ITEM_ROWS;
|
|
|
+ $sheet->insertNewRowBefore($insertBefore, $extraRows);
|
|
|
+
|
|
|
+ for ($row = $insertBefore; $row < $insertBefore + $extraRows; $row++) {
|
|
|
+ $sheet->duplicateStyle($sheet->getStyle('A' . ($insertBefore - 1) . ':F' . ($insertBefore - 1)), 'A' . $row . ':F' . $row);
|
|
|
+ $sheet->getRowDimension($row)->setRowHeight($sheet->getRowDimension($insertBefore - 1)->getRowHeight());
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($itemCount < self::TEMPLATE_ITEM_ROWS) {
|
|
|
+ $sheet->removeRow(self::ITEM_START_ROW + $itemCount, self::TEMPLATE_ITEM_ROWS - $itemCount);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private function buildItems($skus, Contractor $contractor): array
|
|
|
{
|
|
|
$grouped = [];
|