This commit is contained in:
liu
2026-08-13 00:35:09 +08:00
parent 91ea91784d
commit 79e3ed4a8a
5 changed files with 66 additions and 18 deletions
File diff suppressed because one or more lines are too long
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Purchase;
use App\Exceptions\RepositoryException;
use App\Exports\PurchaseOrderExport;
use App\Http\Requests\Purchase\PurchaseCellUpdateRequest;
use App\Http\Requests\Purchase\PurchaseRowUpdateRequest;
use App\Models\ProductModel;
@@ -15,11 +16,13 @@ use App\Services\PurchaseGenerateService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\PostRoute;
use Modules\AnnoRoute\Attribute\PutRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
use Modules\Common\Http\Controllers\BaseController;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
/**
@@ -190,6 +193,27 @@ class PurchaseOrderController extends BaseController
);
}
/**
* C2/C3 导出采购单(Excel 表格):?type=all 全品类 / category 仅蔬果分类
*/
#[GetRoute(route: '/{id}/export', authorize: 'export', where: ['id' => '[0-9]+'])]
public function export(int $id, Request $request): Response
{
$purchase = PurchaseOrderModel::find($id);
if (empty($purchase)) {
throw new RepositoryException('采购单不存在');
}
$type = (string) $request->query('type', 'all');
if (! in_array($type, ['all', 'category'], true)) {
throw new RepositoryException('导出类型参数不正确(all 全品类 / category 蔬果分类)');
}
return Excel::download(
new PurchaseOrderExport($purchase, $type),
$purchase->purchase_no . '_采购单.xlsx',
);
}
/**
* 单元格下钻:采购单中某门店某商品的全部订货明细
*/
+12 -15
View File
@@ -14,8 +14,8 @@ use App\Models\UserModel;
use Maatwebsite\Excel\Facades\Excel;
/**
* 采购单导出:xlsx Content-Type / 蔬果分类过滤 / PDF / format 非法拒绝 / 权限拦截 /
* 中文文件名 RFC 5987 编码
* 采购单导出(仅 Excel 表格)xlsx Content-Type / 蔬果分类过滤 / type 非法拒绝 /
* 权限拦截 / 中文文件名 RFC 5987 编码
*/
class ExportTest extends ProcurementTestCase
{
@@ -56,7 +56,7 @@ class ExportTest extends ProcurementTestCase
$purchase = $this->buildPurchaseWithItems();
$this->actingAsSysUser();
$response = $this->get("/purchase/order/{$purchase->id}/export?type=all&format=xlsx");
$response = $this->get("/purchase/order/{$purchase->id}/export");
$response->assertOk();
$this->assertStringContainsString(
@@ -78,7 +78,7 @@ class ExportTest extends ProcurementTestCase
$this->actingAsSysUser();
Excel::fake();
$this->get("/purchase/order/{$purchase->id}/export?type=category&format=xlsx")->assertOk();
$this->get("/purchase/order/{$purchase->id}/export?type=category")->assertOk();
Excel::assertDownloaded(
$purchase->purchase_no . '_采购单.xlsx',
@@ -90,25 +90,22 @@ class ExportTest extends ProcurementTestCase
);
}
/** PDF 导出:application/pdf */
public function test_export_pdf(): void
/** type 参数非法 → 拒绝 */
public function test_export_invalid_type_rejected(): void
{
$purchase = $this->buildPurchaseWithItems();
$this->actingAsSysUser();
$response = $this->get("/purchase/order/{$purchase->id}/export?format=pdf");
$response->assertOk();
$this->assertStringContainsString('application/pdf', (string) $response->headers->get('Content-Type'));
$this->assertStringStartsWith('%PDF', (string) $response->getContent());
$this->get("/purchase/order/{$purchase->id}/export?type=doc")
->assertJsonPath('success', false);
}
/** format 参数非法 → 拒绝 */
public function test_export_invalid_format_rejected(): void
/** 采购单不存在 → 拒绝 */
public function test_export_missing_purchase_rejected(): void
{
$purchase = $this->buildPurchaseWithItems();
$this->actingAsSysUser();
$this->get("/purchase/order/{$purchase->id}/export?format=doc")
$this->get('/purchase/order/99999/export')
->assertJsonPath('success', false);
}
@@ -119,7 +116,7 @@ class ExportTest extends ProcurementTestCase
// 仅持有查询权限的用户
$this->actingAsSysUser(['purchase.order.query']);
$response = $this->get("/purchase/order/{$purchase->id}/export?format=xlsx");
$response = $this->get("/purchase/order/{$purchase->id}/export");
$this->assertFalse($response->json('success'), '缺少权限点应被拦截');
}
}
+11
View File
@@ -1,8 +1,10 @@
import createAxios from '@/utils/request';
import { downloadBlob } from '@/api/common/download.ts';
import type {
IPurchaseCell,
IPurchaseDetail,
IPurchaseStoreSummary,
PurchaseExportType,
} from '@/domain/iPurchaseOrder.ts';
/** 订单商品参数修改 */
@@ -74,3 +76,12 @@ export async function getPurchaseStoreSummary(purchaseId: number, storeId: numbe
params: { store_id: storeId },
});
}
/** C2/C3 导出采购单(Excel 表格):type=all 全品类 / category 仅蔬果分类 */
export async function exportPurchase(id: number, type: PurchaseExportType = 'all') {
return downloadBlob(
`/purchase/order/${id}/export`,
{ type },
`采购单_${id}.xlsx`,
);
}
+18 -2
View File
@@ -3,6 +3,7 @@ import {
Button,
Descriptions,
Drawer,
Dropdown,
Empty,
Form,
Image,
@@ -19,7 +20,7 @@ import {
Tag,
Typography,
} from 'antd';
import { CheckOutlined, EditOutlined } from '@ant-design/icons';
import { CheckOutlined, DownloadOutlined, EditOutlined } from '@ant-design/icons';
import type { TableProps } from 'antd';
import XinTable from '@/components/XinTable';
import type {
@@ -39,6 +40,7 @@ import type {
import { PURCHASE_STATUS_MAP } from '@/domain/iPurchaseOrder.ts';
import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts';
import {
exportPurchase,
getPurchaseCell,
getPurchaseDetail,
getPurchaseStoreSummary,
@@ -485,7 +487,21 @@ const PurchaseOrderPage: React.FC = () => {
const operateRender: XinTableProps<IPurchaseOrder>['operateRender'] = (record) => [
<Button key="detail" size="small" onClick={() => openDetail(record.id!)}>
</Button>
</Button>,
<AuthButton key="export" auth="purchase.order.export">
<Dropdown
menu={{
items: [
{ key: 'all', label: '导出全品类', onClick: () => exportPurchase(record.id!, 'all') },
{ key: 'category', label: '仅导出蔬果分类', onClick: () => exportPurchase(record.id!, 'category') },
],
}}
>
<Button size="small" type="primary" ghost icon={<DownloadOutlined />}>
</Button>
</Dropdown>
</AuthButton>,
];
const tableProps: XinTableProps<IPurchaseOrder> = {