导出
This commit is contained in:
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',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单元格下钻:采购单中某门店某商品的全部订货明细
|
||||
*/
|
||||
|
||||
@@ -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'), '缺少权限点应被拦截');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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> = {
|
||||
|
||||
Reference in New Issue
Block a user