Compare commits

...

144 Commits

Author SHA1 Message Date
Lei OT cf47dd44a8 Merge remote-tracking branch 'origin/main' 3 days ago
Lei OT 40ca0bdf8d perf: 滚动条 3 days ago
LiaoYijun 87d1e0d3ae fix: 解决预览 Doc 重复刷新 3 days ago
Lei OT 5694081269 perf: 产品管理: 编辑: 允许不选语种提交 3 days ago
LiaoYijun 2b01fc01ea 2.0.20 5 days ago
LiaoYijun 1e3e9b9942 Merge remote-tracking branch 'origin/火车出票模块' 5 days ago
Ycc 534dd60140 站点更新说明 6 days ago
Ycc 0a6a630441 添加站点英文名 6 days ago
Ycc 7e135f349e 优化账单查询 2 weeks ago
Ycc 894f6e8173 sync 3 weeks ago
Ycc b845280151 车票信息编辑 1 month ago
Ycc 4174f33f08 车票信息编辑 1 month ago
Ycc 093fb9833a 火车计划列表 1 month ago
Ycc eda2b2ac2d 查询火车计划列表 1 month ago
Ycc 7acc8a91d7 初始化火车出票模块 2 months ago
LiaoYijun 062b6e21ee 2.0.19 2 months ago
Lei OT f79b4d4caa perf: 产品管理: 多语言描述限制输入字数2000 2 months ago
LiaoYijun 660bec8594 perf: 增加火车票供应商权限 2 months ago
Lei OT d803964131 2.0.18 3 months ago
Lei OT 80f9bb1b48 fix: 人等排序; fix: 海纳是甲方 3 months ago
LiaoYijun f2f8f0216e 2.0.17 5 months ago
LiaoYijun 1fd49cd587 2.0.16 5 months ago
赵鹏 102774dd64 虚拟订单更换一个新订单,旧虚拟订单用新页面显示。 5 months ago
LiaoYijun 7d4b4d3546 2.0.15 6 months ago
LiaoYijun 0602962a83 fix: enddate 增加本月最后一天和时间 23:59 6 months ago
Lei OT 115f913c7f 2.0.14 7 months ago
Lei OT 813258881f perf: 导出合同: 车费, 增加时段列, 显示价格行的时段信息; 景点的绑定: 仅显示`项目名称-类型`; 包价: 全年的时段不显示, 与车费一样按时段分行 7 months ago
Lei OT 9adf82de12 2.0.13 7 months ago
Lei OT e963322eb0 chore: 更新pagespy 前端sdk 7 months ago
LiaoYijun f2b344cae9 2.0.12 7 months ago
eddie 581d9d1b20 update export doc 7 months ago
eddie 563a744629 包价路线,全年的价格无需展示时间 7 months ago
eddie b7f3f2ae14 导出合同文档备注栏按照序号换行 7 months ago
Jimmy Liow 09755c3665 2.0.11 7 months ago
Jimmy Liow 0bf5d43599 perf: 删除无用的文件 7 months ago
eddie a35b7cf037 移除lodash 7 months ago
eddie 48acdad5b7 移除lodash 7 months ago
eddie 87e7c0acc4 使用1120版本合同导出模版文件 7 months ago
eddie 7b15478640 添加新版本导出模版文件 7 months ago
Eddie 1f85a341f6 Merge branch 'main' of github.com:hainatravel/GHHub 7 months ago
Eddie 78834f8f11 添加新的导出文档的class 类 7 months ago
Ycc df1eae2b86 2.0.10 8 months ago
Ycc fc494b57fc 账单审核状态查询,统计勾选记录的价格,修复城市不完整问题 8 months ago
Jimmy Liow b2df11beb3 2.0.9 8 months ago
Lei OT a2ce2534b6 Merge remote-tracking branch 'origin/main' 8 months ago
Lei OT be3aea599b fix: 报价信显示类型 8 months ago
Jimmy Liow 4b2669227e 2.0.8 8 months ago
Lei OT 503547de78 perf: 所有类型增加【报价信显示】; 默认 `计划和报价信都要显示` 8 months ago
Lei OT 202ba82042 perf: 在【餐费、车费、导游】增加【报价信显示】 8 months ago
Jimmy Liow be0b3d2766 2.0.7 8 months ago
Ycc 0f50b5b482 Merge branch 'main' of github.com:hainatravel/GHHub 8 months ago
Jimmy Liow 2f7e8a9f0f 2.0.6 8 months ago
Jimmy Liow 9a80374e24 feat: 上传发票图片使用 CDN 地址 8 months ago
Ycc aee8f94b40 2.0.6 9 months ago
Ycc 2d3d23e10f 自适应手机端 9 months ago
Ycc e4093fcce7 添加提醒 9 months ago
Ycc e462485057 首次进入页面搜索 9 months ago
Ycc 46235a43ac 2.0.5 9 months ago
Ycc 575e792481 显示审核进度 9 months ago
Ycc 8330244008 机票汇款记录查询界面 9 months ago
Lei OT a7b0c870de 2.0.4 9 months ago
Lei OT aa3b055276 revert: 搜索组件: 产品同步状态 9 months ago
Ycc 4baf742268 添加按团状态和出票状态筛选 9 months ago
Ycc b82c7ef7cb 必填限制和标题修改 9 months ago
Ycc 43fc1e5e2d 打开计划预览 9 months ago
Ycc 324d8c0674 解决modal界面闪动问题 9 months ago
Ycc 9c3cdd6ea6 界面优化 9 months ago
Jimmy Liow 001ce6740d 2.0.3 9 months ago
赵鹏 c29c970c4e 图片上传改为国际地址 9 months ago
Jimmy Liow efd576f487 Merge branch 'main' of github.com:hainatravel/GHHub 9 months ago
Jimmy Liow 65cfb289a2 feat: 增加海外接口地址 9 months ago
Lei OT 7917786e9b perf: 产品管理: `新增产品` 权限 9 months ago
Jimmy Liow 1dd04972c6 feat: 添加“新增产品”权限定义 9 months ago
Jimmy Liow 09d8d99059 perf: 整理 cmd 文件 9 months ago
Ycc cbd7bbc8e1 调整默认查询日期 9 months ago
Ycc e5a0342d7b Merge branch 'main' of github.com:hainatravel/GHHub 9 months ago
Ycc 7296ed954c 下拉选择航空公司 9 months ago
Jimmy Liow 0de97b9ea8 fix: 客服和地接共用产品管理导航 9 months ago
Jimmy Liow 7ccc775791 fix: 缺少 useLocation 9 months ago
Jimmy Liow 9d789723a8 2.0.2 9 months ago
Jimmy Liow bb4eca48f6 perf: 调增产品工具栏按钮及位置 9 months ago
Lei OT fb48ac668f perf: 产品管理: 产品列表树: 名称显示 9 months ago
Ycc 171a5f94c4 Merge branch 'main' of github.com:hainatravel/GHHub 9 months ago
Ycc 59427a1a3a 添加导出功能,重复添加费用信息 9 months ago
Lei OT ef8046d1ae 2.0.1 9 months ago
Lei OT 024f066075 fix: 产品管理: 编辑页面: 无管理权限的头部参数 9 months ago
Lei OT f37e5302a1 Merge remote-tracking branch 'origin/main' 9 months ago
Lei OT c28d68960e fix: 产品管理: 国内供应商导航 9 months ago
Ycc 0ad7f3a470 Merge branch 'main' of github.com:hainatravel/GHHub 10 months ago
Jimmy Liow eb410adeb5 2.0.0 10 months ago
Jimmy Liow c88adc4b5e perf: 增加发布小版本批处理 10 months ago
Jimmy Liow ad81a60d55 feat: 增加返回顶部按钮
fix: 合同保存成功后关闭对话框
perf: 优化 main.jsx
10 months ago
Ycc 5aa4d1766d 对齐输入框 10 months ago
Lei OT a7954e1114 2.0.0-rc.9 10 months ago
Ycc bde0e82ece 改为日期控件 10 months ago
Ycc ec2e499abf Merge branch 'main' of github.com:hainatravel/GHHub 10 months ago
Ycc 0af62c2e39 必填项标记 10 months ago
Lei OT 8c16e0503e Merge remote-tracking branch 'origin/main' 10 months ago
Lei OT 7c3bfdfefe fix: docx: 人等匹配问题; # 浙江中青旅数据无法导出 10 months ago
Jimmy Liow 6b396633cb feat: 增加获取合同备注异常提示 10 months ago
Lei OT b439abba39 2.0.0-rc.8 10 months ago
Ycc 1407d05f43 Merge branch 'main' of github.com:hainatravel/GHHub 10 months ago
Ycc c6283f0cc2 新增航班和删除航班功能 10 months ago
Lei OT e66597d4fa fix: 产品管理: 编辑: 语种信息的编辑状态, 保存后更新 10 months ago
Lei OT 5d56479f35 fix: 产品管理: 绑定项目: 搜索的参数travel agency id 10 months ago
Lei OT df1a1f43bf fix: 产品管理: 编辑: 推荐指数, 自由输入 1-1000 10 months ago
Lei OT 3bf491d8ad fix: 产品管理: 编辑: 推荐指数, 自由输入 10 months ago
Lei OT f182ded513 fix: 产品管理: 编辑: 推荐指数 10 months ago
Lei OT 78793d3ddf Merge remote-tracking branch 'origin/main' 10 months ago
Lei OT 4dca7ee290 fix: 产品管: 审核人名称 10 months ago
Jimmy Liow 0cc9a0bb17 2.0.0-rc.7 10 months ago
Jimmy Liow 596dcbd5b0 fix: 增加合同备注表单 10 months ago
Jimmy Liow 6cb61a850a Merge branch 'main' of github.com:hainatravel/GHHub 10 months ago
Jimmy Liow 2e535eedfb feat: 完成合同备注功能 10 months ago
Lei OT 903efaead2 perf: docx: 附加项目: `不分人等` 10 months ago
Lei OT 65f2b996aa perf: docx: 人等最大值 10 months ago
Lei OT 592d396d78 perf: 产品编辑: 基本信息, 语种信息的编辑状态 10 months ago
YCC 741e2e17d4 Merge branch 'main' of github.com:hainatravel/GHHub 10 months ago
Jimmy Liow fc78bd3e58 2.0.0-rc.6 10 months ago
Lei OT ab82ef8d17 Merge remote-tracking branch 'origin/main' 10 months ago
Jimmy Liow 9054538ce3 perf: 增加合同备注界面原型 10 months ago
Lei OT d0af7acae3 perf: 产品管理: 编辑: 表格样式 10 months ago
Lei OT 9ebab45035 perf: 产品编辑: 基本信息, 语种信息的编辑状态 10 months ago
YCC 0dd8cc7a69 调整表格字段位置 10 months ago
Jimmy Liow 7593637786 fix: 解决产品管理没有权限也有导航 10 months ago
YCC 945a1a7a17 解决invoice的图片显示问题 10 months ago
Jimmy Liow 7d4b7bbba1 perf: 使用Message全局提示上传 10 months ago
Jimmy Liow c5343dd4b2 2.0.0-rc.5 10 months ago
Jimmy Liow 4671179614 feat: 增加上传PageSpy离线日志 10 months ago
YCC ff8c072e07 token名称的统一 10 months ago
Jimmy Liow d33cbc625b 2.0.0-rc.4 10 months ago
Jimmy Liow 4c1855d65b feat: 取消令牌时间过期检测 10 months ago
Lei OT 5a4a3f0214 2.0.0-rc.3 10 months ago
Lei OT 93cf0e5c5e Merge remote-tracking branch 'origin/main' 10 months ago
Lei OT 0bb1f24977 fix: 产品管理: 编辑: 保存后绑定项目未显示; 删除绑定项目1; 10 months ago
Jimmy Liow 4951196018 2.0.0-rc.2 10 months ago
Jimmy Liow 3a63b11b3d feat: 预览文档增加随机数 10 months ago
YCC aba7c69a86 Merge branch 'main' of github.com:hainatravel/GHHub 10 months ago
YCC e3f8262cff 预览文档增加随机数 10 months ago
Lei OT fe58f01f22 perf: 产品管理: 编辑保存后, 不更新树的显示状态 10 months ago
Lei OT 7045af0881 perf: 产品管理: 导出docx: +v0903 10 months ago
Lei OT 1f574acab3 perf: 产品管理: `名称`编辑更新到中文语种`名称` 10 months ago
Lei OT df64cc306a todo: 产品管理: 导出docx: +v0903 10 months ago
Lei OT 866433419c perf: 产品管理: 导出docx 10 months ago

1
.gitignore vendored

@ -24,3 +24,4 @@ dist-ssr
*.sw?
/package-lock.json
pnpm-lock.yaml

@ -0,0 +1 @@
npm version patch

@ -61,11 +61,15 @@ INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('所有国内功能', '/domestic/all', 'domestic')
INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('所有机票功能', '/air-ticket/all', 'air-ticket')
INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('所有火车票功能', '/train-ticket/all', 'train-ticket')
-- 价格管理
INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('管理产品', '/products/*', 'products')
INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('新增产品', '/products/new', 'products')
INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('审核信息', '/products/info/audit', 'products')
INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category])
VALUES ('录入信息', '/products/info/put', 'products')

Binary file not shown.

@ -1,7 +1,7 @@
{
"name": "global-highlights-hub",
"private": true,
"version": "2.0.0-rc.1",
"version": "2.0.20",
"type": "module",
"scripts": {
"dev": "vite",
@ -11,8 +11,10 @@
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons": "^5.5.1",
"@react-pdf/renderer": "^3.4.0",
"antd": "^5.17.2",
"dayjs": "^1.11.13",
"docx": "^8.5.0",
"file-saver": "^2.0.5",
"i18next": "^23.11.5",
@ -23,6 +25,7 @@
"react-i18next": "^14.1.2",
"react-router-dom": "^6.10.0",
"react-to-pdf": "^1.0.1",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.18.11/xlsx-0.18.11.tgz",
"zustand": "^4.5.2"
},
"devDependencies": {

@ -88,6 +88,7 @@
"Notice": "Notice",
"Report": "Report",
"Airticket": "AirTicket",
"Trainticket": "TrainTicket",
"Products": "Products"
},
"Validation": {

@ -88,6 +88,7 @@
"Notice": "通知",
"Report": "质量评分",
"Airticket": "机票订票",
"Trainticket": "火车订票",
"Products": "产品管理"
},
"Validation": {

@ -1,5 +1,6 @@
{
"ProductType": "项目类型",
"ContractRemarks": "合同备注",
"type": {
"Experience": "综费",
"Car": "车费",

@ -0,0 +1,94 @@
import React, { useState, useEffect } from "react";
import { Tag, Button, message } from 'antd';
import { CaretUpOutlined, CaretDownOutlined, DownloadOutlined } from '@ant-design/icons';
import { utils, writeFile } from "xlsx";
import { isEmpty, getNestedValue } from "../utils/commons";
/**
* @property diffPercent
* @property diffData
* @property data1
* @property data2
*/
export const VSTag = (props) => {
const { diffPercent, diffData, data1, data2 } = props;
const CaretIcon = parseInt(diffPercent) < 0 ? CaretDownOutlined : CaretUpOutlined;
const tagColor = parseInt(diffPercent) < 0 ? 'gold' : 'lime';
return parseInt(diffPercent) === 0 ? (
'-'
) : (
<span>
{/* <div>
{data1} vs {data2}
</div> */}
<Tag icon={<CaretIcon />} color={tagColor}>
{diffPercent}<span>%</span>{' '}<span>{diffData}</span>
</Tag>
</span>
);
};
/**
* 导出表格数据存为xlsx
*/
export const TableExportBtn = (props) => {
const output_name = `${props.label}`;
const [columnsMap, setColumnsMap] = useState([]);
const [summaryRow, setSummaryRow] = useState({});
useEffect(() => {
const r1 = props.columns.reduce((r, v) => ({
...r,
...(v.children ? v.children.reduce((rc, vc, ci) => ({
...rc,
...(vc?.titleX ? {[`${v?.titleX || v.title},${vc.titleX}`]: vc.titleX } : {[(v?.titleX || v.title) + (ci || '')]: `${vc?.titleX || vc?.title || ''}`}),
}), {}) : {})
}), {});
const flatCols = props.columns.flatMap((v, k) =>
v.children ? v.children.map((vc, ci) => ({ ...vc, title: `${v?.titleX || v.title}` + (vc?.titleX ? `,${vc.titleX}` : (ci || '')) })) : {...v, title: `${v?.titleX || v.title}`}
);
// .filter((c) => c.dataIndex)
// !['string', 'number'].includes(typeof vc.title) ? `${v?.titleX || v.title}` : `${v?.titleX || v.title}-${vc.title || ''}`
;
setColumnsMap(flatCols);
// console.log('flatCols', flatCols);
setSummaryRow(r1);
// console.log('summaryRow', r1);
return () => {};
}, [props.columns]);
const onExport = () => {
if (isEmpty(props.dataSource)) {
message.warning('无结果.');
return false;
}
const data = props.dataSource.map((item) => {
const itemMapped = columnsMap.reduce((sv, kset) => {
const render_val = typeof kset?.render === 'function' ? kset.render('', item) : null;
const data_val = kset?.dataIndex ? (Array.isArray(kset.dataIndex) ? getNestedValue(item, kset.dataIndex) : item[kset.dataIndex]) : undefined;
const x_val = item[`${kset.dataIndex}_X`];
// const _title = kset.title.replace('-[object Object]', '');
const v = { [kset.title]: x_val || data_val || render_val };
return { ...sv, ...v };
}, {});
return itemMapped;
});
const ws = utils.json_to_sheet([].concat(isEmpty(summaryRow) ? [] : [summaryRow], data), { header: columnsMap.filter((r) => r.dataIndex).map((r) => r.title) });
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, 'sheet');
writeFile(wb, `${output_name}.xlsx`);
};
return (
<Button
type="link"
icon={<DownloadOutlined />}
size="small"
disabled={false}
onClick={onExport}
>
{props.btnTxt || '导出excel'}
</Button>
);
};

@ -39,11 +39,12 @@ const SearchForm = ({ initialValue, onSubmit, onReset, onMounted, confirmText, f
referenceNo: { key: "referenceNo", transform: value => value || "" },
dates: [
{ key: "startdate", transform: arrVal => (arrVal ? arrVal[0].format(DATE_FORMAT) : "") },
{ key: "enddate", transform: arrVal => (arrVal ? arrVal[1].format(DATE_FORMAT) : "") },
{ key: "enddate", transform: arrVal => (arrVal ? arrVal[1].endOf('month').format(SMALL_DATETIME_FORMAT) : "") },
{ key: "starttime", transform: arrVal => (arrVal ? arrVal[0].format(DATE_FORMAT) : "") },
{ key: "endtime", transform: arrVal => (arrVal ? arrVal[1].format(SMALL_DATETIME_FORMAT) : "") },
],
invoiceStatus: { key: "invoiceStatus", transform: value => value?.value || value?.key || "", default: "" },
invoiceCheckStatus: { key: "invoiceCheckStatus", transform: value => value?.value || value?.key || "", default: "" },
audit_state: { key: "audit_state", transform: value => value?.value || value?.key || "", default: "" },
agency: {
key: "agency",
@ -71,6 +72,8 @@ const SearchForm = ({ initialValue, onSubmit, onReset, onMounted, confirmText, f
return Array.isArray(value) ? value.map(ele => ele.key).join(",") : value ? value.value : "";
},
},
plan_state: { key: "plan_state", transform: value => value?.value || value?.key || "", default: "" },
airticket_state: { key: "airticket_state", transform: value => value?.value || value?.key || "", default: "" },
unconfirmed: { key: "unconfirmed", transform: value => (value ? 1 : 0) },
};
let dest = {};
@ -179,7 +182,7 @@ function getFields(props) {
let baseChildren = [];
baseChildren = [
item(
"keyword",
"keyword", //
99,
<Form.Item name="keyword" {...fieldProps.keyword}>
<Input allowClear {...fieldComProps.keyword} />
@ -187,7 +190,7 @@ function getFields(props) {
fieldProps?.keyword?.col || 6
),
item(
"referenceNo",
"referenceNo", //
99,
<Form.Item name="referenceNo" label={t("group:RefNo")} {...fieldProps.referenceNo}>
<Input placeholder={t("group:RefNo")} allowClear />
@ -195,7 +198,7 @@ function getFields(props) {
fieldProps?.referenceNo?.col || 6
),
item(
"PNR",
"PNR", //PNR
99,
<Form.Item name="PNR" label="PNR">
<Input placeholder={t("group:PNR")} allowClear />
@ -203,7 +206,7 @@ function getFields(props) {
fieldProps?.PNR?.col || 4
),
item(
"invoiceStatus",
"invoiceStatus", //
99,
<Form.Item name={`invoiceStatus`} initialValue={at(props, "initialValue.invoiceStatus")[0] || { value: "0", label: t("invoiceStatus.Status") }}>
<Select
@ -221,7 +224,23 @@ function getFields(props) {
fieldProps?.invoiceStatus?.col || 3
),
item(
"dates",
"invoiceCheckStatus", //
99,
<Form.Item name={`invoiceCheckStatus`} initialValue={at(props, "initialValue.invoiceCheckStatus")[0] || { value: "0", label: "全部"}}>
<Select
labelInValue
options={[
{ value: "0", label: "全部" },
{ value: "1", label: "待提交" },
{ value: "2", label: "已提交待审核" },
{ value: "3", label: "已完成" },
]}
/>
</Form.Item>,
fieldProps?.invoiceCheckStatus?.col || 3
),
item(
"dates", //
99,
<Form.Item name={"dates"} label={t("group:ArrivalDate")} {...fieldProps.dates} initialValue={at(props, "initialValue.dates")[0]}>
{/* <DatePickerCharts isform={true} {...fieldProps.dates} form={form} /> */}
@ -230,7 +249,7 @@ function getFields(props) {
fieldProps?.dates?.col || midCol
),
item(
"username",
"username", //
99,
<Form.Item name="username" label={t("account:username")} {...fieldProps.username}>
<Input placeholder={t("account:username")} allowClear />
@ -241,7 +260,7 @@ function getFields(props) {
*
*/
item(
"year",
"year", //
99,
<Form.Item name={"year"} label={t("products:UseYear")} {...fieldProps.year} initialValue={at(props, "initialValue.year")[0]}>
<DatePicker picker="year" allowClear {...fieldComProps.year} />
@ -249,7 +268,7 @@ function getFields(props) {
fieldProps?.year?.col || 3
),
item(
"agency",
"agency", //
99,
<Form.Item name="agency" label={t("products:Vendor")} {...fieldProps.agency} initialValue={at(props, "initialValue.agency")[0]}>
<VendorSelector {...fieldComProps.agency} />
@ -257,7 +276,7 @@ function getFields(props) {
fieldProps?.agency?.col || 6
),
item(
"audit_state",
"audit_state", // : /
99,
<Form.Item name={`audit_state`} initialValue={at(props, "initialValue.audit_state")[0] || { value: "", label: "Status" }}>
<AuditStateSelector {...fieldComProps.audit_state} />
@ -265,7 +284,40 @@ function getFields(props) {
fieldProps?.audit_state?.col || 3
),
item(
"products_types",
"airticket_state", //-1 0 1
99,
<Form.Item name="airticket_state" label="出票状态" initialValue={at(props, "initialValue.airticket_state")[0] || { value: "-1", label: "所有" }}>
<Select
labelInValue
options={[
{ value: "-1", label: "所有" },
{ value: "0", label: "未出票" },
{ value: "1", label: "已出票" },
]}
/>
</Form.Item>,
fieldProps?.airticket_state?.col || 4
),
item(
"plan_state", //-1 0123
99,
<Form.Item name="plan_state" label="计划状态" initialValue={at(props, "initialValue.plan_state")[0] || { value: "-1", label: "所有" }}>
<Select
labelInValue
options={[
{ value: "-1", label: "所有" },
{ value: "0", label: "新计划" },
{ value: "1", label: "已确认" },
{ value: "2", label: "有变更" },
{ value: "3", label: "已取消" },
]}
/>
</Form.Item>,
fieldProps?.plan_state?.col || 4
),
item(
"products_types", //
99,
<Form.Item name={`products_types`} label={t("products:ProductType")} {...fieldProps.products_types} initialValue={at(props, "initialValue.products_types")[0] || undefined}>
<ProductsTypesSelector maxTagCount={1} {...fieldComProps.products_types} />
@ -273,7 +325,7 @@ function getFields(props) {
fieldProps?.products_types?.col || 6
),
item(
"dept",
"dept", //
99,
<Form.Item name={`dept`} label={t("products:Dept")} {...fieldProps.dept} initialValue={at(props, "initialValue.dept")[0] || undefined}>
<DeptSelector {...fieldComProps.dept} />
@ -281,7 +333,7 @@ function getFields(props) {
fieldProps?.dept?.col || 6
),
item(
"city",
"city", //
99,
<Form.Item name={`city`} label={t("products:City")} {...fieldProps.city} initialValue={at(props, "initialValue.city")[0] || undefined}>
<CitySelector {...fieldComProps.city} />
@ -289,7 +341,7 @@ function getFields(props) {
fieldProps?.city?.col || 4
),
item(
"unconfirmed",
"unconfirmed", //
99,
<Form.Item name={`unconfirmed`} valuePropName="checked" initialValue={at(props, "initialValue.unconfirmed") || false}>
<Checkbox>{t("group:unconfirmed")}</Checkbox>

@ -10,8 +10,8 @@ const HeaderWrapper = ({ children, header, loading, backTo, ...props }) => {
} = theme.useToken();
return (
<>
<Spin spinning={loading || false}>
<Layout className=' bg-white'>
<Spin spinning={loading || false} wrapperClassName='h-full [&_.ant-spin-container]:h-full' >
<Layout className=' bg-white h-full'>
<Header className='header px-6 h-10 ' style={{ background: 'white' }}>
<Flex justify={'space-between'} align={'center'} className='h-full'>
{/* {header} */}
@ -20,7 +20,7 @@ const HeaderWrapper = ({ children, header, loading, backTo, ...props }) => {
</Flex>
</Header>
<Divider className='my-2' />
<Content className='' style={{ backgroundColor: colorBgContainer }}>
<Content className='overflow-auto' style={{ backgroundColor: colorBgContainer }}>
{children || <Outlet />}
</Content>
</Layout>

@ -3,6 +3,7 @@ export const PROJECT_NAME = "GHHub";
// mode: test内部测试使用
export const HT_HOST = import.meta.env.MODE === 'test' ? 'http://120.79.9.217:10024' : import.meta.env.PROD ? 'https://p9axztuwd7x8a7.mycht.cn' : 'http://202.103.68.144:890'
export const OVERSEA_HOST = 'https://ht20-p9axztuwd7x8a7.mycht.cn'
export const DATE_FORMAT = "YYYY-MM-DD";
export const DATE_FORMAT_MONTH = "YYYY-MM";
@ -33,8 +34,13 @@ export const PERM_DOMESTIC = '/domestic/all'
// category: air-ticket
export const PERM_AIR_TICKET = '/air-ticket/all'
// 火车票供应商
// category: train-ticket
export const PERM_TRAIN_TICKET = '/train-ticket/all'
// 价格管理
export const PERM_PRODUCTS_MANAGEMENT = '/products/*'; // 管理
export const PERM_PRODUCTS_NEW = '/products/new'; // 新增产品
export const PERM_PRODUCTS_INFO_AUDIT = '/products/info/audit'; // 信息.审核
export const PERM_PRODUCTS_INFO_PUT = '/products/info/put'; // 信息.录入
export const PERM_PRODUCTS_OFFER_AUDIT = '/products/offer/audit'; // 价格.审核

@ -105,14 +105,14 @@ export const useProductsTypesFieldsets = (type) => {
const infoDisplay = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? ['display_to_c'] : [];
const infoRecDisplay = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? ['recommends_rate'] : [];
const infoTypesMap = {
'6': [[], []],
'B': [['km'], []],
'J': [[...infoRecDisplay, 'duration', ], ['description']],
'Q': [[...infoRecDisplay, 'duration', ], ['description']],
'6': [[...infoDisplay], []],
'B': [['km', ...infoDisplay], []],
'J': [[...infoRecDisplay, 'duration', ...infoDisplay], ['description']],
'Q': [[...infoRecDisplay, 'duration', ...infoDisplay], ['description']],
'D': [[...infoRecDisplay, 'duration', ...infoDisplay], ['description']],
'7': [[...infoRecDisplay, 'duration', 'open_weekdays'], ['description']],
'R': [[], ['description']],
'8': [[], []],
'7': [[...infoRecDisplay, 'duration', 'open_weekdays', ...infoDisplay], ['description']],
'R': [[...infoDisplay], ['description']],
'8': [[...infoDisplay], []],
};
const thisTypeFieldset = (_type) => {
if (isEmpty(_type)) {
@ -143,7 +143,7 @@ export const useNewProductRecord = () => {
'recommends_rate': 0,
'dept_id': 0,
'dept_name': '',
'display_to_c': 0,
'display_to_c': '0',
'km': 0,
'city_id': 0,
'city_name': '',
@ -151,6 +151,7 @@ export const useNewProductRecord = () => {
'lastedit_changed': '',
'create_date': '',
'created_by': '',
'edit_status': 2,
},
lgc_details: [
{
@ -158,6 +159,7 @@ export const useNewProductRecord = () => {
'descriptions': '',
'lgc': 1,
'id': '',
'edit_status': 2,
},
],
quotation: [

@ -1,5 +1,4 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { createRoot } from 'react-dom/client'
import {
createBrowserRouter,
RouterProvider,
@ -25,11 +24,19 @@ import NoticeIndex from '@/views/notice/Index'
import NoticeDetail from '@/views/notice/Detail'
import InvoiceIndex from '@/views/invoice/Index'
import InvoiceDetail from '@/views/invoice/Detail'
import InvoiceHistory from '@/views/invoice/History'
import InvoicePaid from '@/views/invoice/Paid'
import InvoicePaidDetail from '@/views/invoice/PaidDetail'
import Airticket from '@/views/airticket/Index'
import AirticketPlan from '@/views/airticket/Plan'
import AirticketInvoice from '@/views/airticket/Invoice'
import AirticketInvoicePaid from '@/views/airticket/InvoicePaid'
import Trainticket from '@/views/trainticket/index'
import TrainticketPlan from '@/views/trainticket/plan'
import TrainticketInvoice from '@/views/trainticket/invoice'
import TrainticketInvoicePaid from '@/views/trainticket/invoicePaid'
import { ThemeContext } from '@/stores/ThemeContext'
import { usingStorage } from '@/hooks/usingStorage'
import useAuthStore from './stores/Auth'
@ -38,12 +45,10 @@ import { isNotEmpty } from '@/utils/commons'
import ProductsManage from '@/views/products/Manage';
import ProductsDetail from '@/views/products/Detail';
import ProductsAudit from '@/views/products/Audit';
import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_PUT } from '@/config'
import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA,PERM_TRAIN_TICKET, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_PUT } from '@/config'
import './i18n'
const { createRoot } = ReactDOM
const initRouter = async () => {
return createBrowserRouter([
{
@ -66,11 +71,19 @@ const initRouter = async () => {
{ path: 'notice/:CCP_BLID', element: <NoticeDetail />},
{ path: 'invoice',element:<RequireAuth subject={PERM_OVERSEA} result={true}><InvoiceIndex /></RequireAuth>},
{ path: 'invoice/detail/:GMDSN/:GSN',element:<RequireAuth subject={PERM_OVERSEA} result={true}><InvoiceDetail /></RequireAuth>},
{ path: 'invoice/history/:GMDSN/:GSN',element:<RequireAuth subject={PERM_OVERSEA} result={true}><InvoiceHistory /></RequireAuth>},
{ path: 'invoice/paid',element:<RequireAuth subject={PERM_OVERSEA} result={true}><InvoicePaid /></RequireAuth>},
{ path: 'invoice/paid/detail/:flid', element: <RequireAuth subject={PERM_OVERSEA} result={true}><InvoicePaidDetail /></RequireAuth>},
{ path: 'airticket',element: <RequireAuth subject={PERM_AIR_TICKET} result={true}><Airticket /></RequireAuth>},
{ path: 'airticket/plan/:coli_sn/:gri_sn',element:<RequireAuth subject={PERM_AIR_TICKET} result={true}><AirticketPlan /></RequireAuth>},
{ path: 'airticket/invoice',element:<RequireAuth subject={PERM_AIR_TICKET} result={true}><AirticketInvoice /></RequireAuth>},
{ path: 'airticket/invoicepaid',element:<RequireAuth subject={PERM_AIR_TICKET} result={true}><AirticketInvoicePaid /></RequireAuth>},
{ path: 'trainticket',element: <RequireAuth subject={PERM_TRAIN_TICKET} result={true}><Trainticket /></RequireAuth>},
{ path: 'trainticket/plan/:coli_sn/:gri_sn',element:<RequireAuth subject={PERM_TRAIN_TICKET} result={true}><TrainticketPlan /></RequireAuth>},
{ path: 'trainticket/invoice',element:<RequireAuth subject={PERM_TRAIN_TICKET} result={true}><TrainticketInvoice /></RequireAuth>},
{ path: 'trainticket/invoicepaid',element:<RequireAuth subject={PERM_TRAIN_TICKET} result={true}><TrainticketInvoicePaid /></RequireAuth>},
{ path: "products",element: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT} result={true}><ProductsManage /></RequireAuth>},
{ path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT} result={true}><ProductsAudit /></RequireAuth>},
@ -100,8 +113,11 @@ const initAppliction = async () => {
}
const router = await initRouter()
const root = document.getElementById('root')
if (!root) throw new Error('No root element found')
createRoot(document.getElementById('root')).render(
createRoot(root).render(
//<React.StrictMode>
<ThemeContext.Provider value={{ colorPrimary: '#00b96b', borderRadius: 4 }}>
<RouterProvider

@ -1,26 +1,36 @@
import { loadScript } from '@/utils/commons';
import { PROJECT_NAME } from '@/config';
import { PROJECT_NAME, BUILD_VERSION } from '@/config';
export const loadPageSpy = (title) => {
if (import.meta.env.DEV || window.$pageSpy) return
const PageSpyConfig = { api: 'page-spy.mycht.cn', project: PROJECT_NAME, title: title, autoRender: false };
const PageSpySrc = [
'https://page-spy.mycht.cn/page-spy/index.min.js',
'https://page-spy.mycht.cn/plugin/data-harbor/index.min.js',
'https://page-spy.mycht.cn/plugin/rrweb/index.min.js',
'https://page-spy.mycht.cn/page-spy/index.min.js'+`?${BUILD_VERSION}`,
'https://page-spy.mycht.cn/plugin/data-harbor/index.min.js'+`?${BUILD_VERSION}`,
'https://page-spy.mycht.cn/plugin/rrweb/index.min.js'+`?${BUILD_VERSION}`,
];
Promise.all(PageSpySrc.map((src) => loadScript(src))).then(() => {
//
PageSpy.registerPlugin(new DataHarborPlugin({ maximum: 2 * 1024 * 1024 }));
// PageSpy
window.$pageSpy = new PageSpy({ api: 'page-spy.mycht.cn', project: PROJECT_NAME, title: title, autoRender: false });
window.$harbor = new DataHarborPlugin();
window.$rrweb = new RRWebPlugin();
[window.$harbor, window.$rrweb].forEach(p => {
PageSpy.registerPlugin(p)
})
window.$pageSpy = new PageSpy(PageSpyConfig);
});
};
export const uploadPageSpyLog = () => {
window.$pageSpy.triggerPlugins('onOfflineLog', 'upload');
export const uploadPageSpyLog = async () => {
// window.$pageSpy.triggerPlugins('onOfflineLog', 'upload');
if (window.$pageSpy) {
await window.$harbor.upload() // { clearCache: true, remark: '' }
alert('Success')
} else {
alert('Failure')
}
}
export const PageSpyLog = () => {

@ -12,8 +12,9 @@ const airTicketStore = create((set, get) => ({
setGuestList: guestList => set({ guestList }),
setVEIFlightBill: vEIFlightBill => set({ vEIFlightBill }),
setVeiPlanChangeTxt: veiPlanChangeTxt => set({ veiPlanChangeTxt }),
setAirPortList: airPortList => set({ airPortList }),
async getPlanList(vei_sn, GRI_Name, TimeStart, TimeEnd) {
async getPlanList(vei_sn, GRI_Name, TimeStart, TimeEnd, plan_state, airticket_state) {
const { setLoading, setPlanList } = get();
setLoading(true);
const searchParams = {
@ -21,6 +22,8 @@ const airTicketStore = create((set, get) => ({
FlightDate1: TimeStart,
FlightDate2: TimeEnd,
GRI_Name: GRI_Name,
FlightStatus: plan_state,
TicketIssued: airticket_state,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetFlightPlan`, searchParams);
@ -38,6 +41,8 @@ const airTicketStore = create((set, get) => ({
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetFlightPlanDetail`, searchParams);
const _result = errcode !== 0 ? [] : result;
setPlanDetail(_result);
//return _result.filter(item => isNotEmpty(item.GRI_No));
return "dsadsd";
},
async postFlightDetail(CLF_SN, GRI_SN, VEI_SN, original_values, info_object) {
@ -51,6 +56,7 @@ const airTicketStore = create((set, get) => ({
for (const [key, value] of Object.entries(info_object)) {
formData.set(key, value); //再用新值覆盖
}
formData.set("StartDate", dayjs(info_object.StartDate).format(DATE_FORMAT)); //再用新值覆盖
//是否出票的值true、false变为1或0
formData.set("TicketIssued", info_object.TicketIssued ? 1 : 0);
const postUrl = HT_HOST + "/Service_BaseInfoWeb/edit_or_new_flight_info";
@ -62,6 +68,15 @@ const airTicketStore = create((set, get) => ({
}
});
},
//删除航班信息
async delete_flight_info(CLF_SN) {
const searchParams = {
CLF_SN: CLF_SN,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/Delete_flight_info`, searchParams);
const _result = errcode !== 0 ? [] : result;
return _result;
},
async getGuestList(coli_sn) {
const { setGuestList } = get();
@ -74,7 +89,7 @@ const airTicketStore = create((set, get) => ({
},
//获取账单列表
async getVEIFlightBill(VEI_SN, GRI_Name, CheckStatus, FlightDate1, FlightDate2) {
const { setLoading,setVEIFlightBill } = get();
const { setLoading, setVEIFlightBill } = get();
setLoading(true);
const searchParams = {
VEI_SN: VEI_SN,
@ -140,7 +155,7 @@ const airTicketStore = create((set, get) => ({
});
},
//提交账单
async postVEIFlightBillSubmit(VEI_SN,values) {
async postVEIFlightBillSubmit(VEI_SN, values) {
const formData = new FormData();
formData.append("vei_sn", VEI_SN);
formData.append("billdata", JSON.stringify(values));
@ -154,15 +169,58 @@ const airTicketStore = create((set, get) => ({
});
},
//通知顾问查看机票信息
async ticketIssuedNotifications(CLF_SN, OPI_SN) {
async ticketIssuedNotifications(LMI_SN, CLF_SN, OPI_SN, FlightMemo_messages) {
const searchParams = {
CLF_SN: CLF_SN,
OPI_SN: OPI_SN,
LMI_SN: LMI_SN,
FlightMemo_messages: FlightMemo_messages,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/TicketIssuedNotifications`, searchParams);
const _result = errcode !== 0 ? [] : result;
return _result;
},
//获取机场列表
async getAirPortList() {
const { setAirPortList } = get();
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetAirPortInfo`);
const _result = errcode !== 0 ? [] : result;
setAirPortList(_result);
},
airLineList: [
{ label: "CA-国航", value: "国航" },
{ label: "MU-东方航空", value: "东方航空" },
{ label: "FM-上海航空", value: "上海航空" },
{ label: "CZ-南方航空", value: "南方航空" },
{ label: "HO-吉祥航空", value: "吉祥航空" },
{ label: "HU-海南航空", value: "海南航空" },
{ label: "ZH-深圳航空", value: "深圳航空" },
{ label: "MF-厦门航空", value: "厦门航空" },
{ label: "3U-四川航空", value: "四川航空" },
{ label: "SC-山东航空", value: "山东航空" },
{ label: "JD-首都航空", value: "首都航空" },
{ label: "BK-奥凯航空", value: "奥凯航空" },
{ label: "GS-天津航空", value: "天津航空" },
{ label: "CN-大新华", value: "大新华" },
{ label: "KN-中联航", value: "中联航" },
{ label: "TV-西藏航空", value: "西藏航空" },
{ label: "8L-祥鹏航空", value: "祥鹏航空" },
{ label: "KY-昆明航空", value: "昆明航空" },
{ label: "EU-成都航空", value: "成都航空" },
{ label: "G5-华夏航空", value: "华夏航空" },
{ label: "NS-河北航空", value: "河北航空" },
{ label: "QW-青岛航空", value: "青岛航空" },
{ label: "Y8-扬子江", value: "扬子江" },
{ label: "PN-西部航空", value: "西部航空" },
{ label: "DZ-东海航空", value: "东海航空" },
{ label: "GT-桂林航空", value: "桂林航空" },
{ label: "9H-长安航空", value: "长安航空" },
{ label: "GY-多彩航空", value: "多彩航空" },
{ label: "DR-瑞丽航空", value: "瑞丽航空" },
{ label: "GJ-长龙航空", value: "长龙航空" },
{ label: "GX-广西北部", value: "广西北部" },
],
}));
export default airTicketStore;

@ -37,6 +37,7 @@ export const fetchPermissionListByUserId = async (userId) => {
return errcode !== 0 ? {} : result
}
// 取消令牌时间过期检测,待删除
async function fetchLastRequet() {
const { errcode, result } = await fetchJSON(`${HT_HOST}/service-CooperateSOA/GetLastReqDate`)
return errcode !== 0 ? {} : result
@ -44,7 +45,6 @@ async function fetchLastRequet() {
const initialState = {
tokenInterval: null,
tokenTimeout: import.meta.env.PROD ? true : false,
loginStatus: 0,
defaltRoute: '',
currentUser: {
@ -62,7 +62,7 @@ const useAuthStore = create(devtools((set, get) => ({
...initialState,
initAuth: async () => {
const { startTokenInterval, loadUserPermission } = get()
const { loadUserPermission } = get()
const { setStorage, loginToken } = usingStorage()
// Dev 模式使用 localStorage会有 token 失效情况,需要手动删除
@ -87,8 +87,7 @@ const useAuthStore = create(devtools((set, get) => ({
}
}))
startTokenInterval()
loadPageSpy(userJson.real_name)
loadPageSpy(`${userJson.real_name}-${userJson.VName}`)
},
authenticate: async (usr, pwd) => {
@ -102,7 +101,6 @@ const useAuthStore = create(devtools((set, get) => ({
await initAuth()
set(() => ({
tokenTimeout: false,
loginStatus: 302
}))
},
@ -139,27 +137,6 @@ const useAuthStore = create(devtools((set, get) => ({
}))
},
startTokenInterval: () => {
const { logout } = get()
async function checkTokenTimeout() {
// TODOToken 失效后要跳转到登录页面
const { LastReqDate } = await fetchLastRequet()
const lastReqDateTime = new Date(LastReqDate).getTime()
const now = new Date()
const diffTime = now.getTime() - lastReqDateTime
const diffHours = diffTime/1000/60/60
if (diffHours > 1) {
logout()
}
}
const interval = setInterval(() => checkTokenTimeout(), 1000*60*10)
set(() => ({
tokenInterval: interval
}))
},
// TODO: 迁移到 Account.js
changeUserPassword: (password, newPassword) => {
const { userId } = usingStorage()

@ -66,7 +66,7 @@ export const delProductExtrasAction = async (body) => {
* @param {object} param { id, travel_agency_id, use_year }
*/
export const getAgencyProductExtrasAction = async (param) => {
const _param = { ...param, use_year: (param.use_year || '').replace('all', '') };
const _param = { ...param, use_year: String(param.use_year || '').replace('all', '') };
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/products_extras`, _param);
return errcode !== 0 ? [] : result;
};
@ -137,6 +137,32 @@ export const deleteQuotationAction = async (id) => {
return { errcode, result, success: errcode === 0 };
}
/**
* 获取合同备注
*/
export const fetchRemarkList = async (params) => {
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_memo_get`, params)
return { errcode, result, success: errcode === 0 }
}
/**
* 获取合同备注
*/
export const postRemarkList = async (params) => {
const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_memo_add`, params)
return { errcode, result, success: errcode === 0 }
}
const defaultRemarkList = [
{id: 0, "product_type_id": "6","Memo": ""},
{id: 0, "product_type_id": "B","Memo": ""},
{id: 0, "product_type_id": "J","Memo": ""},
{id: 0, "product_type_id": "Q","Memo": ""},
{id: 0, "product_type_id": "7","Memo": ""},
{id: 0, "product_type_id": "R","Memo": ""},
{id: 0, "product_type_id": "D","Memo": ""}
]
const initialState = {
loading: false,
searchValues: {}, // 客服首页: 搜索条件
@ -148,7 +174,8 @@ const initialState = {
quotationList: [], // 编辑页: 当前产品报价列表
editing: false,
switchParams: {}, // 头部切换参数
};
}
export const useProductsStore = create(
devtools((set, get) => ({
// 初始化状态
@ -193,6 +220,45 @@ export const useProductsStore = create(
reset: () => set(initialState),
getRemarkList: async() => {
const {switchParams} = get()
const { result, success } = await fetchRemarkList({
travel_agency_id: switchParams.travel_agency_id, use_year: switchParams.use_year
})
if (success) {
const mapRemarkList = defaultRemarkList.map(remark => {
const filterResult = result.filter(r => r.product_type_id === remark.product_type_id)
if (filterResult.length > 0) return filterResult[0]
else return remark
})
return Promise.resolve(mapRemarkList)
} else {
return Promise.resolve('获取合同备注失败')
}
},
saveOrUpdateRemark: async(remarkList) => {
const {switchParams} = get()
const mapRemarkList = remarkList.map(remark => {
return {
id: remark.id,
travel_agency_id: switchParams.travel_agency_id,
use_year: switchParams.use_year,
product_type_id: remark.product_type_id,
Memo: remark.Memo,
}
})
const { result, success } = await postRemarkList(mapRemarkList)
if (success) {
return Promise.resolve(result)
} else {
return Promise.resolve('保存合同备注失败')
}
},
newEmptyQuotation: () => ({
id: null,
adult_cost: 0,

@ -0,0 +1,209 @@
import { create } from "zustand";
import { fetchJSON, postForm } from "@/utils/request";
import { prepareUrl, isNotEmpty } from "@/utils/commons";
import { HT_HOST, DATE_FORMAT } from "@/config";
import dayjs from "dayjs";
const trainTicketStore = create((set, get) => ({
loading: false,
setLoading: loading => set({ loading }),
setPlanList: planList => set({ planList }),
setPlanDetail: planDetail => set({ planDetail }),
setGuestList: guestList => set({ guestList }),
setVEIFlightBill: vEIFlightBill => set({ vEIFlightBill }),
setVeiPlanChangeTxt: veiPlanChangeTxt => set({ veiPlanChangeTxt }),
async getPlanList(vei_sn, GRI_Name, TimeStart, TimeEnd, plan_state, airticket_state) {
const { setLoading, setPlanList } = get();
setLoading(true);
const searchParams = {
vei_sn: vei_sn,
FlightDate1: TimeStart,
FlightDate2: TimeEnd,
GRI_Name: GRI_Name,
FlightStatus: plan_state,
TicketIssued: airticket_state,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetTrainPlan`, searchParams);
const _result = errcode !== 0 ? [] : result;
setPlanList(_result);
setLoading(false);
},
async getPlanDetail(vei_sn, gri_sn) {
const { setPlanDetail } = get();
const searchParams = {
vei_sn: vei_sn,
gri_sn: gri_sn,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetTrainPlanDetail`, searchParams);
const _result = errcode !== 0 ? [] : result;
setPlanDetail(_result);
//return _result.filter(item => isNotEmpty(item.GRI_No));
return "dsadsd";
},
async postFlightDetail(CLF_SN, GRI_SN, VEI_SN, original_values, info_object) {
const formData = new FormData();
formData.append("CLF_SN", CLF_SN ? CLF_SN : "");
formData.append("GRI_SN", GRI_SN);
formData.append("VEI_SN", VEI_SN);
for (const [key, value] of Object.entries(original_values)) {
formData.append(key, value); //先用原始数据填充一遍,确保复制了全部数据到新表
}
for (const [key, value] of Object.entries(info_object)) {
formData.set(key, value); //再用新值覆盖
}
formData.set("StartDate", dayjs(info_object.StartDate).format(DATE_FORMAT)); //再用新值覆盖
//是否出票的值true、false变为1或0
formData.set("TicketIssued", info_object.TicketIssued ? 1 : 0);
const postUrl = HT_HOST + "/Service_BaseInfoWeb/edit_or_new_flight_info";
return postForm(postUrl, formData).then(json => {
if (json.errcode == 0) {
return json;
} else {
throw new Error(json.errmsg + ": " + json.errcode);
}
});
},
//删除火车信息
async delete_flight_info(CLF_SN) {
const searchParams = {
CLF_SN: CLF_SN,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/Delete_flight_info`, searchParams);
const _result = errcode !== 0 ? [] : result;
return _result;
},
async getGuestList(coli_sn) {
const { setGuestList } = get();
const searchParams = {
COLI_SN: coli_sn,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetFlightGuestInfo`, searchParams);
const _result = errcode !== 0 ? [] : result;
setGuestList(_result);
},
//获取账单列表
async getVEIFlightBill(VEI_SN, GRI_Name, CheckStatus, FlightDate1, FlightDate2) {
const { setLoading, setVEIFlightBill } = get();
setLoading(true);
const searchParams = {
VEI_SN: VEI_SN,
GRI_Name: GRI_Name,
CheckStatus: CheckStatus,
FlightDate1: FlightDate1,
FlightDate2: FlightDate2,
ServiceType: 2,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetVEIFlightBill`, searchParams);
const _result = errcode !== 0 ? [] : result;
setVEIFlightBill(_result);
setLoading(false);
},
//保存费用
async postFlightCost(values) {
const formData = new FormData();
for (const [key, value] of Object.entries(values)) {
formData.append(key, value); //先用原始数据填充一遍,确保复制了全部数据到新表
}
const postUrl = HT_HOST + "/Service_BaseInfoWeb/edit_or_new_flight_cost";
return postForm(postUrl, formData).then(json => {
if (json.errcode == 0) {
return json;
} else {
throw new Error(json.errmsg + ": " + json.errcode);
}
});
},
//删除费用
async deleteFlightCost(CLC_SN) {
const searchParams = {
CLC_SN: CLC_SN,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/Delete_flight_cost`, searchParams);
const _result = errcode !== 0 ? [] : result;
return _result;
},
//获取变更信息
async getVeiPlanChange(VEI_SN, GRI_SN) {
const { setVeiPlanChangeTxt } = get();
const searchParams = {
VEI_SN: VEI_SN,
GRI_SN: GRI_SN,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetVeiFlightPlanChange`, searchParams);
const _result = errcode !== 0 ? [] : result;
setVeiPlanChangeTxt(_result);
},
//提交变更确认
async postVeiFlightPlanConfirm(VEI_SN, GRI_SN, LMI_SN, ConfirmInfo) {
const formData = new FormData();
formData.append("VEI_SN", VEI_SN);
formData.append("GRI_SN", GRI_SN);
formData.append("LMI_SN", LMI_SN);
formData.append("ServiceType", 2);
formData.append("ConfirmInfo", ConfirmInfo);
const postUrl = HT_HOST + "/Service_BaseInfoWeb/VeiFlightPlanConfirm";
return postForm(postUrl, formData).then(json => {
if (json.errcode == 0) {
return json;
} else {
throw new Error(json.errmsg + ": " + json.errcode);
}
});
},
//提交账单
async postVEIFlightBillSubmit(VEI_SN, values) {
const formData = new FormData();
formData.append("vei_sn", VEI_SN);
formData.append("ServiceType", 2);
formData.append("billdata", JSON.stringify(values));
const postUrl = HT_HOST + "/Service_BaseInfoWeb/VEIFlightBillSubmit";
return postForm(postUrl, formData).then(json => {
if (json.errcode == 0) {
return json;
} else {
throw new Error(json.errmsg + ": " + json.errcode);
}
});
},
//通知顾问查看车票信息
async ticketIssuedNotifications(LMI_SN, CLF_SN, OPI_SN, FlightMemo_messages) {
const searchParams = {
CLF_SN: CLF_SN,
OPI_SN: OPI_SN,
LMI_SN: LMI_SN,
ServiceType: 2,
FlightMemo_messages: FlightMemo_messages,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/TicketIssuedNotifications`, searchParams);
const _result = errcode !== 0 ? [] : result;
return _result;
},
// 定义座位表对象
seatTable: [
{ code: "9", name: "商务座", enName: "Business Class Seat" },
{ code: "P", name: "特等座", enName: "Superior Seat" },
{ code: "M", name: "一等座", enName: "First Class Seat" },
{ code: "O", name: "二等座", enName: "Second Class Seat" },
{ code: "6", name: "高级软卧", enName: "Superior Soft Sleeper" },
{ code: "5", name: "包厢硬卧", enName: "Hard Sleeper in Private Compartment" },
{ code: "4", name: "软卧", enName: "Soft Sleeper" },
{ code: "F", name: "动卧", enName: "High - Speed Train Sleeper" },
{ code: "3", name: "硬卧", enName: "Hard Sleeper" },
{ code: "2", name: "软座", enName: "Soft Seat" },
{ code: "1", name: "硬座", enName: "Hard Seat" },
{ code: "Q", name: "观光座", enName: "Sightseeing Seat" },
{ code: "H", name: "其它", enName: "Other" },
{ code: "WZ", name: "无座", enName: "Standing - room Only" },
{ code: "YDW", name: "一等卧", enName: "First - Class Sleeper" },
{ code: "EDW", name: "二等卧", enName: "Second - Class Sleeper" },
],
}));
export default trainTicketStore;

@ -1,27 +1,28 @@
import { Outlet, Link, useHref, useNavigate, NavLink } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { Layout, Menu, ConfigProvider, theme, Dropdown, Space, Row, Col, Badge, Typography, Modal, Input, Button, App as AntApp } from 'antd';
import { DownOutlined } from '@ant-design/icons';
import 'antd/dist/reset.css';
import AppLogo from '@/assets/logo-gh.png';
import { isEmpty } from '@/utils/commons';
import { useTranslation } from 'react-i18next';
import zhLocale from 'antd/locale/zh_CN';
import enLocale from 'antd/locale/en_US';
import 'dayjs/locale/zh-cn';
import ErrorBoundary from '@/components/ErrorBoundary';
import { BUILD_VERSION, } from '@/config';
import useNoticeStore from '@/stores/Notice';
import useAuthStore from '@/stores/Auth';
import { useThemeContext } from '@/stores/ThemeContext';
import { usingStorage } from '@/hooks/usingStorage';
import { useDefaultLgc } from '@/i18n/LanguageSwitcher';
import { Outlet, Link, useHref, useNavigate, NavLink } from 'react-router-dom'
import { useEffect, useState } from 'react'
import { Layout, Menu, ConfigProvider, theme, Dropdown, message, FloatButton, Space, Row, Col, Badge, App as AntApp } from 'antd'
import { DownOutlined } from '@ant-design/icons'
import 'antd/dist/reset.css'
import AppLogo from '@/assets/logo-gh.png'
import { isEmpty } from '@/utils/commons'
import { useTranslation } from 'react-i18next'
import zhLocale from 'antd/locale/zh_CN'
import enLocale from 'antd/locale/en_US'
import 'dayjs/locale/zh-cn'
import { BugOutlined } from "@ant-design/icons"
import ErrorBoundary from '@/components/ErrorBoundary'
import { BUILD_VERSION, PERM_PRODUCTS_OFFER_PUT, PERM_PRODUCTS_INFO_PUT } from '@/config'
import useNoticeStore from '@/stores/Notice'
import useAuthStore from '@/stores/Auth'
import { useThemeContext } from '@/stores/ThemeContext'
import { usingStorage } from '@/hooks/usingStorage'
import { useDefaultLgc } from '@/i18n/LanguageSwitcher'
import { appendRequestParams } from '@/utils/request'
import { uploadPageSpyLog } from '@/pageSpy';
import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/config';
import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT,PERM_TRAIN_TICKET } from '@/config'
const { Header, Content, Footer } = Layout;
const { Title } = Typography;
const { Header, Content, Footer } = Layout
function App() {
@ -29,13 +30,13 @@ function App() {
const { colorPrimary } = useThemeContext()
const [password, setPassword] = useState('')
const [authenticate, tokenTimeout, isPermitted, currentUser] = useAuthStore(
(state) => [state.authenticate, state.tokenTimeout, state.isPermitted, state.currentUser])
const [isPermitted, currentUser] = useAuthStore(
(state) => [state.isPermitted, state.currentUser])
const { loginToken } = usingStorage()
const [messageApi, contextHolder] = message.useMessage()
const noticeUnRead = useNoticeStore((state) => state.noticeUnRead)
const href = useHref()
const navigate = useNavigate()
@ -48,15 +49,6 @@ function App() {
}
}, [href])
const onSubmit = () => {
authenticate(currentUser?.username, password)
.catch(ex => {
console.error(ex)
alert(t('Validation.LoginFailed'))
})
setPassword('')
}
const splitPath = href.split('/')
let defaultPath = 'notice'
@ -64,13 +56,26 @@ function App() {
defaultPath = splitPath[1]
}
const { language } = useDefaultLgc();
const [antdLng, setAntdLng] = useState(enLocale);
const { language } = useDefaultLgc()
const [antdLng, setAntdLng] = useState(enLocale)
useEffect(() => {
setAntdLng(i18n.language === 'en' ? enLocale : zhLocale);
appendRequestParams('lgc', language);
setAntdLng(i18n.language === 'en' ? enLocale : zhLocale)
appendRequestParams('lgc', language)
}, [i18n.language])
const uploadLog = () => {
if (window.$pageSpy) {
window.$pageSpy.triggerPlugins('onOfflineLog', 'upload')
messageApi.info('Success')
} else {
messageApi.error('Failure')
}
}
//
const isProductPermitted = isPermitted(PERM_PRODUCTS_MANAGEMENT) || isPermitted(PERM_PRODUCTS_INFO_PUT)
const productLink = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? '/products' : '/products/edit'
return (
<ConfigProvider locale={antdLng}
theme={{
@ -82,27 +87,18 @@ function App() {
algorithm: theme.defaultAlgorithm,
}}>
<AntApp>
<ErrorBoundary>
<Modal
centered
closable={false}
maskClosable={false}
footer={null}
open={tokenTimeout}
<FloatButton.Group
shape='square'
style={{
insetInlineEnd: 94,
}}
>
<Title level={3}>{t('LoginTimeout')}</Title>
<div>{t('LoginTimeoutTip')}</div>
<Space direction='horizontal'>
<Input.Password value={password}
onChange={(e) => setPassword(e.target.value)}
onPressEnter={onSubmit}
addonBefore={currentUser?.username} />
<Button
onClick={onSubmit}
>{t('Submit')}</Button></Space>
</Modal>
<Layout className='min-h-screen'>
<FloatButton icon={<BugOutlined />} onClick={() => uploadPageSpyLog()} />
<FloatButton.BackTop />
</FloatButton.Group>
{contextHolder}
<ErrorBoundary>
<Layout className='min-h-screen h-dvh'>
<Header className='sticky top-0 z-10 w-full'>
<Row gutter={{ md: 24 }} justify='end' align='middle'>
<Col span={15}>
@ -119,7 +115,8 @@ function App() {
isPermitted(PERM_OVERSEA) ? { key: 'feedback', label: <Link to='/feedback'>{t('menu.Feedback')}</Link> } : null,
isPermitted(PERM_OVERSEA) ? { key: 'report', label: <Link to='/report'>{t('menu.Report')}</Link> } : null,
isPermitted(PERM_AIR_TICKET) ? { key: 'airticket', label: <Link to='/airticket'>{t('menu.Airticket')}</Link> } : null,
isPermitted(PERM_PRODUCTS_MANAGEMENT) ? { key: 'products', label: <Link to='/products'>{t('menu.Products')}</Link> } : { key: 'products', label: <Link to='/products/edit'>{t('menu.Products')}</Link> },
isPermitted(PERM_TRAIN_TICKET) ? { key: 'trainticket', label: <Link to='/trainticket'>{t('menu.Trainticket')}</Link> } : null,
isProductPermitted ? { key: 'products', label: <Link to={productLink}>{t('menu.Products')}</Link> } : null,
{
key: 'notice',
label: (
@ -162,7 +159,7 @@ function App() {
</Col>
</Row>
</Header>
<Content className='p-6 m-0 min-h-72 bg-white'>
<Content className='p-6 m-0 min-h-72 bg-white overflow-auto'>
{needToLogin ? <>login...</> : <Outlet />}
</Content>
<Footer>China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}</Footer>

@ -1,15 +1,14 @@
import { Outlet } from 'react-router-dom'
import { Layout, ConfigProvider, theme, Row, Col, App as AntApp } from 'antd'
import 'antd/dist/reset.css'
import AppLogo from '@/assets/logo-gh.png'
import { useThemeContext } from '@/stores/ThemeContext'
import { BUILD_VERSION, } from '@/config';
import { Outlet } from "react-router-dom";
import { Layout, ConfigProvider, theme, Row, Col, App as AntApp } from "antd";
import "antd/dist/reset.css";
import AppLogo from "@/assets/logo-gh.png";
import { useThemeContext } from "@/stores/ThemeContext";
import { BUILD_VERSION } from "@/config";
const { Header, Content, Footer } = Layout
const { Header, Content, Footer } = Layout;
function Standlone() {
const { colorPrimary } = useThemeContext()
const { colorPrimary } = useThemeContext();
return (
<ConfigProvider
@ -20,23 +19,19 @@ function Standlone() {
algorithm: theme.defaultAlgorithm,
}}>
<AntApp>
<Layout className='min-h-screen'>
<Header className='sticky top-0 z-10 w-full'>
<Row gutter={{ md: 24 }} justify='center'>
<Col span={2}>
<img src={AppLogo} className='float-left h-9 my-4 mr-6 ml-0 bg-white/30' alt='App logo' />
</Col>
<Col span={5}><h1 className='text-white text-center'>Global Highlights Hub</h1></Col>
</Row>
<Layout className="min-h-screen">
<Header className="sticky top-0 z-10 w-full">
<img src={AppLogo} className="float-left h-9 my-4 mr-6 ml-0 bg-white/30" alt="App logo" />
<p className="text-white text-center">Global Highlights Hub</p>
</Header>
<Content className='p-6 m-0 min-h-72 bg-white'>
<Content className="p-6 m-0 min-h-72 bg-white">
<Outlet />
</Content>
<Footer>China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}</Footer>
<Footer>China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}</Footer>
</Layout>
</AntApp>
</ConfigProvider>
)
);
}
export default Standlone
export default Standlone;

@ -128,6 +128,11 @@ function Management() {
const onAccountFinish = (values) => {
saveOrUpdateAccount(values)
.then(() => {
notification.info({
message: 'Notification',
description: '账号保存成功',
placement: 'top',
})
setAccountModalOpen(false)
handelAccountSearch()
})

@ -1,6 +1,6 @@
import { Descriptions, Col, Row } from 'antd';
import { useTranslation } from 'react-i18next';
import useAuthStore from '@/stores/Auth';
import { Descriptions, Col, Row } from 'antd'
import { useTranslation } from 'react-i18next'
import useAuthStore from '@/stores/Auth'
function Profile() {
@ -12,7 +12,7 @@ function Profile() {
<Col span={12} offset={6}>
<Descriptions title={t('userProfile')} layout="vertical" column={2}>
<Descriptions.Item label={t("Username")}>{currentUser?.username}</Descriptions.Item>
<Descriptions.Item label={t("Realname")}>{currentUser?.realname}({currentUser?.rolesName})</Descriptions.Item>
<Descriptions.Item label={t("Realname")}>{currentUser?.realname}</Descriptions.Item>
<Descriptions.Item label={t("Email")}>{currentUser?.emailAddress}</Descriptions.Item>
<Descriptions.Item label={t("Company")}>{currentUser?.travelAgencyName}</Descriptions.Item>
</Descriptions>
@ -21,4 +21,4 @@ function Profile() {
);
}
export default Profile;
export default Profile

@ -68,6 +68,7 @@ function RoleList() {
['oversea', '海外供应商'],
['domestic', '国内供应商'],
['air-ticket', '机票供应商'],
['train-ticket', '火车票供应商'],
['products', '产品价格'],
['page', '默认页面'],
]);

@ -1,11 +1,12 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Spin, Input, Col, Row, Space, List, Table, Button } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined,AuditOutlined } from "@ant-design/icons";
import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined, AuditOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
import dayjs from "dayjs";
import SearchForm from "@/components/SearchForm";
import { DATE_FORMAT } from "@/config";
import { TableExportBtn } from "@/components/Data";
import airTicketStore from "@/stores/Airticket";
import { usingStorage } from "@/hooks/usingStorage";
@ -64,13 +65,13 @@ const planListColumns = [
render: text => formatColonTime(text),
},
{
title: "是否出票",
title: "出票处理",
key: "TicketIssued",
dataIndex: "TicketIssued",
render: (text, record) => record.TicketIssuedName,
},
{
title: "状态",
title: "计划状态",
key: "FlightStatus",
dataIndex: "FlightStatus",
render: (text, record) => record.FlightStatusName,
@ -89,7 +90,9 @@ const Airticket = props => {
const [getPlanList, planList, loading] = airTicketStore(state => [state.getPlanList, state.planList, state.loading]);
const showTotal = total => `合计 ${total} `;
useEffect(() => {}, []);
useEffect(() => {
!planList && getPlanList(travelAgencyId, "", dayjs().startOf("M").format(DATE_FORMAT), dayjs().add(3, "M").endOf("M").format(DATE_FORMAT), "-1", "-1");
}, []);
return (
<Space direction="vertical" style={{ width: "100%" }}>
@ -97,30 +100,36 @@ const Airticket = props => {
<Col md={20} lg={20} xxl={20}>
<SearchForm
initialValue={{
dates: [dayjs().startOf("M"), dayjs().endOf("M")],
dates: [dayjs().startOf("M"), dayjs().add(3, "M").endOf("M")],
}}
fieldsConfig={{
shows: ["referenceNo", "dates"],
shows: ["referenceNo", "dates", "airticket_state", "plan_state"],
fieldProps: {
referenceNo: { label: "搜索计划" },
dates: { label: "出发日期" ,col:8},
dates: { label: "出发日期", col: 8 },
},
}}
onSubmit={(err, formVal, filedsVal) => {
getPlanList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.endtime);
getPlanList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.endtime, formVal.plan_state, formVal.airticket_state);
}}
/>
</Col>
<Col md={4} lg={4} xxl={4}>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/airticket/invoice`)}>
报账
</Button>
<Space>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/airticket/invoice`)}>
报账
</Button>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/airticket/invoicepaid`)}>
汇款记录
</Button>
</Space>
</Col>
</Row>
<Row gutter={16}>
<Col md={24} lg={24} xxl={24}>
<Table bordered={true} rowKey="id" columns={planListColumns} dataSource={planList} loading={loading} pagination={{ defaultPageSize: 20, showTotal: showTotal }} />
<TableExportBtn btnTxt="导出计划" label={`机票计划`} {...{ columns: planListColumns, dataSource: planList }} />
</Col>
<Col md={24} lg={24} xxl={24}></Col>
</Row>

@ -1,11 +1,12 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Spin, Input, Col, Row, Space, Checkbox, Table, Button, App } from "antd";
import { Grid, Divider, Layout, Steps, Statistic, Col, Row, Space, Checkbox, Table, Button, App, Typography } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, FrownTwoTone, LikeTwoTone } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
import dayjs from "dayjs";
import SearchForm from "@/components/SearchForm";
import BackBtn from "@/components/BackBtn";
import { TableExportBtn } from "@/components/Data";
import airTicketStore from "@/stores/Airticket";
import { usingStorage } from "@/hooks/usingStorage";
@ -23,22 +24,17 @@ const Invoice = props => {
title: "团名",
key: "GRI_No",
dataIndex: "GRI_No",
render: (text, record) => `${record.GRI_No} ${record.WL}`,
render: (text, record) => (
<Typography.Text title={record.Memo}>
{record.GRI_No} {record.WL}
</Typography.Text>
),
},
{
title: "费用类型",
title: "状态",
key: "CostType",
dataIndex: "CostType",
},
{
title: "手续费",
children: [
{
title: vEIFlightBill && vEIFlightBill.reduce((acc, curr) => acc + curr.ServiceFee, 0),
dataIndex: "ServiceFee",
},
],
},
{
title: "出发日期",
key: "StartDate",
@ -50,10 +46,16 @@ const Invoice = props => {
},
},
{
title: "城市",
title: "出发",
key: "FromCity",
dataIndex: "FromCity",
render: (text, record) => (record.CostType == "出票" ? `${record.FromCity} - ${record.ToCity}` : "-"),
render: (text, record) => (record.CostType == "出票" ? `${record.FromCity}` : "-"),
},
{
title: "抵达",
key: "ToCity",
dataIndex: "ToCity",
render: (text, record) => (record.CostType == "出票" ? `${record.ToCity}` : "-"),
},
{
title: "航班",
@ -90,17 +92,21 @@ const Invoice = props => {
],
key: "Cost",
},
{
title: "服务费",
children: [
{
title: vEIFlightBill && vEIFlightBill.reduce((acc, curr) => acc + curr.ServiceFee, 0),
dataIndex: "ServiceFee",
},
],
},
{
title: "折扣",
key: "Discount",
dataIndex: "Discount",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "备注",
key: "Memo",
dataIndex: "Memo",
},
{
title: "审核状态",
children: [
@ -117,10 +123,28 @@ const Invoice = props => {
待提交
</Checkbox>
) : (
record.CheckStatusName
<Steps
size="small"
current={record.CheckStatus - 1}
items={[
{
title: "提交账单",
},
{
title: "顾问审核",
},
{
title: "财务处理",
},
{
title: "账单支付",
},
]}
/>
),
},
],
sorter: (a, b) => {
return b.CheckStatus - a.CheckStatus;
},
@ -132,7 +156,7 @@ const Invoice = props => {
};
// checkbox
const handleCheckboxChange = (event, data) => {
const value = { CLC_SN: data.CLC_SN, WL: data.WL, OPI_SN: data.OPI_SN, OPI_Email: data.OPI_Email, GRI_SN: data.GRI_SN, GRI_Name: data.GRI_Name };
const value = { CLC_SN: data.CLC_SN, WL: data.WL, OPI_SN: data.OPI_SN, OPI_Email: data.OPI_Email, GRI_SN: data.GRI_SN, GRI_Name: data.GRI_Name, Cost: data.Cost, ServiceFee: data.ServiceFee };
if (event.target.checked) {
setSelectedValues([...selectedValues, value]); //
} else {
@ -166,7 +190,13 @@ const Invoice = props => {
const checkALL = () => {
if (isEmpty(vEIFlightBill)) return;
const allChecked = selectedValues.length === vEIFlightBill.filter(item => item.CheckStatus < 2).length;
setSelectedValues(allChecked ? [] : vEIFlightBill.filter(item => item.CheckStatus < 2).map(item => ({ CLC_SN: item.CLC_SN, OPI_SN: item.OPI_SN }))); //
setSelectedValues(
allChecked
? []
: vEIFlightBill
.filter(item => item.CheckStatus < 2)
.map(item => ({ CLC_SN: item.CLC_SN, WL: item.WL, OPI_SN: item.OPI_SN, OPI_Email: item.OPI_Email, GRI_SN: item.GRI_SN, GRI_Name: item.GRI_Name, Cost: item.Cost, ServiceFee: item.ServiceFee }))
); //
};
useEffect(() => {}, []);
@ -180,14 +210,14 @@ const Invoice = props => {
dates: [dayjs().startOf("M"), dayjs().endOf("M")],
}}
fieldsConfig={{
shows: ["referenceNo", "dates", "invoiceStatus"],
shows: ["referenceNo", "dates", "invoiceCheckStatus"],
fieldProps: {
referenceNo: { label: "搜索计划" },
dates: { label: "出发日期", col: 8 },
},
}}
onSubmit={(err, formVal, filedsVal) => {
getVEIFlightBill(travelAgencyId, formVal.referenceNo, formVal.invoiceStatus, formVal.startdate, formVal.endtime);
getVEIFlightBill(travelAgencyId, formVal.referenceNo, formVal.invoiceCheckStatus, formVal.startdate, formVal.endtime);
}}
/>
</Col>
@ -199,12 +229,20 @@ const Invoice = props => {
<Row>
<Col md={24} lg={24} xxl={24}>
<Table bordered={true} rowKey="CLC_SN" columns={vEIFlightBillColumns} dataSource={vEIFlightBill} loading={loading} pagination={{ defaultPageSize: 100, showTotal: showTotal }} />
<TableExportBtn btnTxt="导出账单" label={`机票账单`} {...{ columns: vEIFlightBillColumns, dataSource: vEIFlightBill }} />
</Col>
<Col md={24} lg={24} xxl={24}></Col>
</Row>
<Row>
<Col md={24} lg={22} xxl={22}></Col>
<Col md={24} lg={18} xxl={18}></Col>
<Col md={24} lg={2} xxl={2}>
<Statistic title="已选机票价格" value={selectedValues.reduce((acc, curr) => acc + curr.Cost, 0)} />
</Col>
<Col md={24} lg={2} xxl={2}>
<Statistic title="已选服务费" value={selectedValues.reduce((acc, curr) => acc + curr.ServiceFee, 0)} />
</Col>
<Col md={24} lg={2} xxl={2}>
<Button type="primary" size="large" onClick={postInvoice} disabled={selectedValues.length == 0}>
提交账单

@ -0,0 +1,137 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Spin, Input, Col, Row, Space, Checkbox, Table, Button, App } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, FrownTwoTone, LikeTwoTone } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime, formatDate, isNotEmpty } from "@/utils/commons";
import { DATE_FORMAT } from "@/config";
import dayjs from "dayjs";
import SearchForm from "@/components/SearchForm";
import BackBtn from "@/components/BackBtn";
import { TableExportBtn } from "@/components/Data";
import useInvoiceStore from "@/stores/Invoice";
import { fetchInvoicePaidDetail } from "@/stores/Invoice";
import airTicketStore from "@/stores/Airticket";
import { usingStorage } from "@/hooks/usingStorage";
const InvoicePaid = props => {
const navigate = useNavigate();
const { notification } = App.useApp();
const { travelAgencyId } = usingStorage();
const [invoicePaidDetail, setInvoicePaidDetail] = useState([]);
const [invoiceNO, setInvoiceNO] = useState([]); //
const [loading, invoicePaid, fetchInvoicePaid] = useInvoiceStore(state => [state.loading, state.invoicePaid, state.fetchInvoicePaid]);
const showTotal = total => `Total ${total} items`;
const showTotal_detail = total => `Total ${total} items`;
useEffect(() => {
// fetchInvoicePaid(travelAgencyId, "", dayjs().subtract(2, "M").startOf("M").format(DATE_FORMAT), dayjs().endOf("M").format(DATE_FORMAT));
}, []);
const invoicePaidColumns = [
{
title: "编号",
dataIndex: "fl_finaceNo",
key: "fl_finaceNo",
},
{
title: "报账日期",
key: "fl_adddate",
dataIndex: "fl_adddate",
render: (text, record) => (isNotEmpty(text) ? formatDate(new Date(text)) : ""),
},
{
title: "团数",
key: "fcount",
dataIndex: "fcount",
},
{
title: "总额",
key: "pSum",
dataIndex: "pSum",
//render: (text, record) => (isNotEmpty(record.GMD_Currency) ? record.GMD_Currency + " " + text : text),
},
{
title: "查看",
key: "pSum",
dataIndex: "pSum",
render: (text, record) => (
<Button
type="link"
onClick={() => {
fetchInvoicePaidDetail(travelAgencyId, record.key).then(res => setInvoicePaidDetail(res));
setInvoiceNO(record.fl_finaceNo);
}}>
查看明细
</Button>
),
},
];
const invoicePaidDetailColumns = [
{
title: "团号",
dataIndex: "fl2_GroupName",
key: "fl2_GroupName",
},
{
title: "金额",
key: "fl2_price",
dataIndex: "fl2_price",
},
{
title: "报账日期",
key: "fl2_ArriveDate",
dataIndex: "fl2_ArriveDate",
render: (text, record) => (isNotEmpty(text) ? formatDate(new Date(text)) : ""),
},
{
title: "顾问",
dataIndex: "fl2_wl",
key: "fl2_wl",
},
];
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row>
<Col md={20} lg={20} xxl={20}>
<SearchForm
initialValue={{
dates: [dayjs().subtract(2, "M").startOf("M"), dayjs().endOf("M")],
}}
fieldsConfig={{
shows: ["dates"],
fieldProps: {
dates: { col: 10, label: "报账日期" },
},
}}
onSubmit={(err, formVal) => {
fetchInvoicePaid(travelAgencyId, "", formVal.startdate, formVal.enddate);
setInvoicePaidDetail([]);
}}
/>
</Col>
<Col md={4} lg={4} xxl={4}>
<BackBtn to={"/airticket"} />
</Col>
</Row>
<Row>
<Col md={24} lg={16} xxl={16}>
<Divider orientation="left">汇款列表</Divider>
<Table bordered columns={invoicePaidColumns} dataSource={invoicePaid} loading={loading} pagination={{ defaultPageSize: 20, showTotal: showTotal }} />
</Col>
<Col md={24} lg={4} xxl={4}></Col>
</Row>
<Row>
<Col md={24} lg={20} xxl={20}>
<Divider orientation="left">账单明细 {invoiceNO}</Divider>
<Table bordered columns={invoicePaidDetailColumns} dataSource={invoicePaidDetail} pagination={{ defaultPageSize: 100, showTotal: showTotal_detail }} />
<TableExportBtn btnTxt="导出账单明细" label={`机票账单`} {...{ columns: invoicePaidDetailColumns, dataSource: invoicePaidDetail }} />
</Col>
<Col md={24} lg={4} xxl={4}></Col>
</Row>
</Space>
);
};
export default InvoicePaid;

@ -1,10 +1,10 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Modal, Form, Input, Col, Row, Space, Collapse, Table, Button, Select, App, Popconfirm, Switch } from "antd";
import { PhoneOutlined, FrownTwoTone, LikeTwoTone, ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons";
import { Checkbox, Divider, DatePicker, Modal, Form, Input, Col, Row, Space, Collapse, Table, Button, Select, App, Popconfirm, Switch, Radio, List } from "antd";
import { PhoneOutlined, FrownTwoTone, LikeTwoTone, ArrowUpOutlined, ArrowDownOutlined, PlusOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
import { OFFICEWEBVIEWERURL } from "@/config";
import dayjs from "dayjs";
import airTicketStore from "@/stores/Airticket";
import { usingStorage } from "@/hooks/usingStorage";
import BackBtn from "@/components/BackBtn";
@ -12,7 +12,24 @@ import BackBtn from "@/components/BackBtn";
const AirticketPlan = props => {
const { coli_sn, gri_sn } = useParams();
const { travelAgencyId, loginToken, userId } = usingStorage();
const [getPlanDetail, planDetail, getGuestList, guestList, loading, postFlightDetail, postFlightCost, deleteFlightCost, getVeiPlanChange, veiPlanChangeTxt, postVeiFlightPlanConfirm, ticketIssuedNotifications] = airTicketStore(state => [
const [
getPlanDetail,
planDetail,
getGuestList,
guestList,
loading,
postFlightDetail,
postFlightCost,
deleteFlightCost,
getVeiPlanChange,
veiPlanChangeTxt,
postVeiFlightPlanConfirm,
ticketIssuedNotifications,
delete_flight_info,
getAirPortList,
airPortList,
airLineList,
] = airTicketStore(state => [
state.getPlanDetail,
state.planDetail,
state.getGuestList,
@ -25,6 +42,10 @@ const AirticketPlan = props => {
state.veiPlanChangeTxt,
state.postVeiFlightPlanConfirm,
state.ticketIssuedNotifications,
state.delete_flight_info,
state.getAirPortList,
state.airPortList,
state.airLineList,
]);
const reservationUrl = `https://p9axztuwd7x8a7.mycht.cn/Service_BaseInfoWeb/FlightPlanDocx?GRI_SN=${gri_sn}&VEI_SN=${travelAgencyId}&token=${loginToken}`;
const reservationPreviewUrl = OFFICEWEBVIEWERURL + encodeURIComponent(reservationUrl);
@ -32,7 +53,7 @@ const AirticketPlan = props => {
const { notification } = App.useApp();
//console.log(reservationPreviewUrl);
//
//
const guestList_select = () => {
return (
guestList &&
@ -42,21 +63,36 @@ const AirticketPlan = props => {
);
};
const guestList_OnChange = value => {
ticket_form.setFieldsValue({ Memo: `${value}` });
const guestList_OnChange = e => {
ticket_form.setFieldsValue({ Memo: `${e.target.value}` });
};
//
const airPortList_select = () => {
return (
airPortList &&
airPortList.map(item => {
return { label: `${item.AirPort_Code} - ${item.AirPort_Name}`, value: item.AirPort_Name };
})
);
};
//
const costListColumns = [
{
title: "费用类型",
title: "客人信息/备注",
key: "Memo",
dataIndex: "Memo",
},
{
title: "状态",
key: "CostType",
dataIndex: "CostType",
},
{
title: "手续费",
key: "ServiceFee",
dataIndex: "ServiceFee",
title: "票号",
key: "TicketNo",
dataIndex: "TicketNo",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "PNR",
@ -64,12 +100,7 @@ const AirticketPlan = props => {
dataIndex: "PNR",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "票号",
key: "TicketNo",
dataIndex: "TicketNo",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "机票类型",
key: "FlightType",
@ -82,17 +113,18 @@ const AirticketPlan = props => {
dataIndex: "Cost",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "服务费",
key: "ServiceFee",
dataIndex: "ServiceFee",
},
{
title: "折扣",
key: "Discount",
dataIndex: "Discount",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "备注",
key: "Memo",
dataIndex: "Memo",
},
{
title: "编辑",
key: "CLC_SN",
@ -115,160 +147,176 @@ const AirticketPlan = props => {
const Airticket_form = props => {
const airInfo = props.airInfo;
const [airinfo_form] = Form.useForm();
return (
<>
<Row>
<Col md={4} lg={4} xxl={4}></Col>
<Col md={16} lg={16} xxl={16}>
<Divider orientation="left">航班信息</Divider>
<Form
name={"ticket_form_" + airInfo.id}
labelCol={{
span: 6,
}}
wrapperCol={{
span: 16,
}}
initialValues={airInfo}
onFinish={values => {
postFlightDetail(airInfo.CLF_SN, airInfo.GRI_SN, airInfo.VEI_SN, airInfo, values)
.then(() => {
notification.success({
message: `成功`,
description: "机票信息保存成功!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
})
.catch(() => {
notification.error({
message: `错误`,
description: "保存失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
}}
autoComplete="off">
<Form.Item label="城市">
<Form
form={airinfo_form}
name={"ticket_form_" + airInfo.id}
labelCol={{
span: 6,
}}
wrapperCol={{
span: 16,
}}
initialValues={{ ...airInfo, StartDate: dayjs(airInfo.StartDate) }}
onFinish={values => {
postFlightDetail(airInfo.CLF_SN, airInfo.GRI_SN, airInfo.VEI_SN, airInfo, values)
.then(() => {
notification.success({
message: `成功`,
description: "机票信息保存成功!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
})
.catch(() => {
notification.error({
message: `错误`,
description: "保存失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
}}
autoComplete="off">
<Divider orientation="left">航班信息</Divider>
<Row gutter={16}>
<Col md={24} lg={20} xxl={20}>
<Form.Item label="出发日期、航班、城市、时间" required>
<Space>
<Form.Item name="FromCity" noStyle>
<Input placeholder="出发" prefix={<ArrowUpOutlined />} />
<Form.Item name="StartDate" noStyle rules={[{ required: true, message: "请输入出发日期!" }]}>
<DatePicker
style={{
minWidth: 160,
}}
/>
</Form.Item>
<Form.Item name="ToCity" noStyle>
<Input placeholder="抵达" prefix={<ArrowDownOutlined />} />
<Form.Item name="FlightNo" noStyle rules={[{ required: true, message: "请输入航班号!" }]}>
<Input placeholder="航班号" />
</Form.Item>
</Space>
</Form.Item>
<Form.Item label="出发日期、航司、航班">
<Space>
<Form.Item name="StartDate" noStyle>
<Input placeholder="出发日期" />
<Form.Item name="FromCity" noStyle rules={[{ required: true, message: "请输入出发城市!" }]}>
<Input placeholder="出发" />
</Form.Item>
<Form.Item name="FlightCompany" noStyle>
<Input placeholder="航空公司" />
<Form.Item name="FlightStart" noStyle rules={[{ required: true, message: "请输入出发时间!" }]}>
<Input placeholder="出发时间" />
</Form.Item>
<Form.Item name="FlightNo" noStyle>
<Input placeholder="航班号" />
-
<Form.Item name="ToCity" noStyle rules={[{ required: true, message: "请输入抵达城市!" }]}>
<Input placeholder="抵达" />
</Form.Item>
<Form.Item name="FlightEnd" noStyle rules={[{ required: true, message: "请输入抵达时间!" }]}>
<Input placeholder="抵达时间" />
</Form.Item>
</Space>
</Form.Item>
<Form.Item label="出发">
<Form.Item label="机场、航站楼、仓位、行李重量" required>
<Space>
<Form.Item name="FromAirport" noStyle>
<Input placeholder="机场" />
<Form.Item name="FromAirport" noStyle rules={[{ required: true, message: "请输入出发机场!" }]}>
<Select
showSearch
placeholder="出发机场"
style={{
minWidth: 160,
}}
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
options={airPortList_select()}
/>
</Form.Item>
<Form.Item name="FromTerminal" noStyle>
<Form.Item name="FromTerminal" noStyle rules={[{ required: true, message: "请输入出发航站楼!" }]}>
<Input placeholder="航站楼" />
</Form.Item>
<Form.Item name="FlightStart" noStyle>
<Input placeholder="出发时间" />
-
<Form.Item name="ToAirport" noStyle rules={[{ required: true, message: "请输入抵达机场!" }]}>
<Select
showSearch
placeholder="抵达机场"
style={{
minWidth: 160,
}}
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
options={airPortList_select()}
/>
</Form.Item>
</Space>
</Form.Item>
<Form.Item label="抵达">
<Space>
<Form.Item name="ToAirport" noStyle>
<Input placeholder="机场" />
</Form.Item>
<Form.Item name="ToTerminal" noStyle>
<Form.Item name="ToTerminal" noStyle rules={[{ required: true, message: "请输入抵达航站楼!" }]}>
<Input placeholder="航站楼" />
</Form.Item>
<Form.Item name="FlightEnd" noStyle>
<Input placeholder="抵达时间" />
</Form.Item>
</Space>
</Form.Item>
<Form.Item label="仓位和行李">
<Space>
<Form.Item name="FlightCabin" noStyle>
<Form.Item name="FlightCabin" noStyle rules={[{ required: true, message: "请输入仓位!" }]}>
<Input placeholder="仓位" />
</Form.Item>
<Form.Item name="Baggage" noStyle>
<Input placeholder="行李说明" />
<Input placeholder="行李说明 20KG" />
</Form.Item>
</Space>
</Form.Item>
<Form.Item label="备注" name="FlightMemo">
<Input.TextArea rows={5} />
</Col>
<Col md={24} lg={4} xxl={4}>
<Space direction="vertical">
<Form.Item name="TicketIssued">
<Switch checkedChildren="已处理" unCheckedChildren="未处理" />
</Form.Item>
<Button type="primary" htmlType="submit">
1. 保存机票信息
</Button>
</Space>
</Col>
</Row>
<Divider orientation="left">出票信息</Divider>
<Row gutter={16}>
<Col md={24} lg={20} xxl={20}>
<Table bordered={true} rowKey="CLC_SN" columns={costListColumns} dataSource={airInfo.Flightcost_AsJOSN} loading={loading} pagination={false} />
</Col>
<Col md={24} lg={4} xxl={4}>
<Space direction="vertical">
<Button type="primary" onClick={() => showModal(airInfo)}>
2. 添加出票信息
</Button>
</Space>
</Col>
</Row>
<Divider orientation="left"></Divider>
<Row gutter={16}>
<Col md={24} lg={20} xxl={20}>
<Form.Item label="提醒信息" name="FlightMemo_messages">
<Input placeholder="没有提醒请留空,信息会抄送给上下站地接" />
</Form.Item>
<Form.Item label="已出票" name="TicketIssued">
<Switch checkedChildren="是" unCheckedChildren="否" />
<Form.Item label="已发提醒" name="FlightMemo">
<Input.TextArea rows={4} readOnly disabled />
</Form.Item>
<Form.Item
wrapperCol={{
offset: 10,
span: 16,
</Col>
<Col md={24} lg={4} xxl={4}>
<Button
type="primary"
onClick={() => {
ticketIssuedNotifications(userId,airInfo.CLF_SN, airInfo.OPI_SN,airinfo_form.getFieldValue('FlightMemo_messages'))
.then(() => {
notification.success({
message: `成功`,
description: "提醒信息已发出!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
airinfo_form.setFieldValue('FlightMemo_messages','')
})
.catch(() => {
notification.error({
message: `错误`,
description: "提醒失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
}}>
<Space>
<Button type="primary" htmlType="submit">
1. 保存机票信息
</Button>
<Button type="primary" onClick={() => showModal(airInfo)}>
2. 添加出票信息或费用
</Button>
<Button
type="primary"
onClick={() => {
ticketIssuedNotifications(airInfo.CLF_SN, airInfo.OPI_SN)
.then(() => {
notification.success({
message: `成功`,
description: "提醒信息已发出!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
})
.catch(() => {
notification.error({
message: `错误`,
description: "提醒失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
}}>
3. 通知顾问
</Button>
</Space>
</Form.Item>
</Form>
</Col>
<Col md={4} lg={4} xxl={4}></Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24}>
<Divider orientation="left">费用列表</Divider>
<Table bordered={true} rowKey="CLC_SN" columns={costListColumns} dataSource={airInfo.Flightcost_AsJOSN} loading={loading} pagination={false} />
</Col>
</Row>
3. 通知顾问
</Button>
</Col>
</Row>
</Form>
</>
);
};
@ -279,7 +327,21 @@ const AirticketPlan = props => {
return {
key: item.id,
label: `${item.StartDate} ${item.FlightNo}(${item.FromAirport}${item.FlightStart}-${item.ToAirport}${item.FlightEnd})(${item.FlightCabin})`,
extra: `${item.GRI_No}-${item.WL}`,
extra: (
<Popconfirm
title="请确认要删除航班记录"
description=""
onConfirm={() => {
delete_flight_info(item.CLF_SN); //
getPlanDetail(travelAgencyId, gri_sn); //
}}
okText="是"
cancelText="否">
<Button type="dashed" size="small" disabled={item.Flightcost_AsJOSN.length == 0 ? false : true}>
删除
</Button>
</Popconfirm>
),
children: <Airticket_form airInfo={item} />,
};
})
@ -287,20 +349,25 @@ const AirticketPlan = props => {
};
// begin
const [isModalOpen, setIsModalOpen] = useState(false);
const [isModalOpen_confirmInfo, setisModalOpen_confirmInfo] = useState(false);
const [isTicketType, setisTicketType] = useState(true);
const [isAddNew, setisAddNew] = useState(true); //
const [ticket_form] = Form.useForm();
const [confirmInfo_form] = Form.useForm();
const showModal = ticket => {
setIsModalOpen(true);
ticket_form.resetFields();
if (isEmpty(ticket.CostType)) ticket.CostType = "出票";
ticket.CostType == "出票" ? setisTicketType(true) : setisTicketType(false); //
isEmpty(ticket.CLC_SN) ? setisAddNew(true) : setisAddNew(false); //
ticket_form.setFieldsValue(ticket);
if (isEmpty(ticket.Memo)) ticket_form.setFieldsValue({ Memo: "" });
};
const handleOk = () => {
const handleOk = (close_modal = true) => {
ticket_form
.validateFields()
.then(values => {
@ -326,8 +393,7 @@ const AirticketPlan = props => {
icon: <FrownTwoTone />,
});
});
ticket_form.resetFields();
setIsModalOpen(false);
if (close_modal) setIsModalOpen(false);
})
.catch(info => {
console.log("Validate Failed:", info);
@ -370,120 +436,6 @@ const AirticketPlan = props => {
}
};
const TicketModal = () => {
return (
<>
<Modal title="费用信息" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
<Form
form={ticket_form}
labelCol={{
span: 5,
}}>
<Form.Item label="费用类型" name="CostType">
<Select
style={{
width: 160,
}}
onChange={onChangeType}
options={[
{
value: "出票",
label: "出票",
},
{
value: "改签",
label: "改签",
},
{
value: "退票",
label: "退票",
},
{
value: "其它",
label: "其它",
},
]}
/>
</Form.Item>
<Form.Item label="手续费/费用" name="ServiceFee" rules={[{ required: true }]}>
<Input
prefix="¥"
style={{
width: 160,
}}
/>
</Form.Item>
{isTicketType && (
<>
<Form.Item label="PNR" name="PNR">
<Input />
</Form.Item>
<Form.Item label="票号" name="TicketNo">
<Input />
</Form.Item>
<Form.Item label="机票类型" name="FlightType">
<Select
style={{
width: 160,
}}
options={[
{
value: "成人",
label: "成人",
},
{
value: "儿童",
label: "儿童",
},
{
value: "婴儿",
label: "婴儿",
},
]}
/>
</Form.Item>
<Form.Item label="机票价格" name="Cost">
<Input
placeholder="含基建和税"
prefix="¥"
style={{
width: 160,
}}
/>
</Form.Item>
<Form.Item label="折扣" name="Discount">
<Input
style={{
width: 160,
}}
/>
</Form.Item>
<Form.Item label="选择客人" name="MEI_Name66">
<Select onChange={value => guestList_OnChange(value)} options={guestList_select()} />
</Form.Item>
</>
)}
<Form.Item label="备注" name="Memo">
<Input.TextArea rows={4} />
</Form.Item>
<Form.Item name="CLF_SN" hidden>
<input />{" "}
</Form.Item>
<Form.Item name="GRI_SN" hidden>
<input />
</Form.Item>
<Form.Item name="VEI_SN" hidden>
<input />
</Form.Item>
<Form.Item name="CLC_SN" hidden>
<input />
</Form.Item>
</Form>
</Modal>
</>
);
};
//
const showModal_confirmInfo = ConfirmInfo => {
setisModalOpen_confirmInfo(true);
@ -526,24 +478,6 @@ const AirticketPlan = props => {
console.log("Validate Failed:", info);
});
};
//
const ConfirmInfoModal = () => {
return (
<>
<Modal title="变更" open={isModalOpen_confirmInfo} onOk={handleOk_confirmInfo} onCancel={handleCancel_confirmInfo}>
<Form
form={confirmInfo_form}
labelCol={{
span: 5,
}}>
<Form.Item label="确认信息" name="ConfirmInfo" rules={[{ required: true }]}>
<Input.TextArea rows={4} />
</Form.Item>
</Form>
</Modal>
</>
);
};
// end
@ -551,6 +485,7 @@ const AirticketPlan = props => {
getPlanDetail(travelAgencyId, gri_sn); //
getGuestList(coli_sn); //
getVeiPlanChange(travelAgencyId, gri_sn); //
getAirPortList(); //
}, []);
return (
@ -564,7 +499,7 @@ const AirticketPlan = props => {
<Row>
<Col md={24} lg={24} xxl={24} style={{ height: "100%" }}>
<iframe id="msdoc-iframe-reservation" title="msdoc-iframe-reservation" src={reservationPreviewUrl} style={{ width: "100%", height: "600px" }}></iframe>
<iframe id="msdoc-iframe-reservation" title="msdoc-iframe-reservation" src={reservationPreviewUrl + "&v=" + Math.random()} style={{ width: "100%", height: "600px" }}></iframe>
<Button type="link" target="_blank" href={reservationUrl}>
下载
</Button>
@ -572,16 +507,35 @@ const AirticketPlan = props => {
</Row>
<Row>
<Divider orientation="left">出票信息</Divider>
<Divider orientation="center">{planDetail ? `${planDetail[0].GRI_No} - ${planDetail[0].WL}` : ""}</Divider>
<Col md={24} lg={24} xxl={24}>
<Collapse items={detail_items()} />
</Col>
<Col md={24} lg={24} xxl={24}>
<br />
<p style={{ textAlign: "right" }}>
<Popconfirm
title="请确认要增加航班记录"
description=""
onConfirm={() => {
postFlightDetail("", gri_sn, travelAgencyId, { FlightNo: "新的记录", FlightStatus: 1 }, []); //
getPlanDetail(travelAgencyId, gri_sn); //
}}
okText="是"
cancelText="否">
<Button type="dashed" icon={<PlusOutlined />}>
新增航班记录
</Button>
</Popconfirm>
</p>
</Col>
</Row>
<Row>
<Divider orientation="left">计划变更</Divider>
<Col md={24} lg={12} xxl={12}>
<Space direction="vertical" style={{ width: "90%" }}>
<Input.TextArea rows={4} readOnly value={veiPlanChangeTxt && veiPlanChangeTxt.ChangeText} />
<Input.TextArea rows={16} readOnly value={veiPlanChangeTxt && veiPlanChangeTxt.ChangeText} />
<Button
type="primary"
onClick={() => {
@ -597,8 +551,170 @@ const AirticketPlan = props => {
</Col>
</Row>
<TicketModal />
<ConfirmInfoModal />
<Modal title="变更" open={isModalOpen_confirmInfo} onOk={handleOk_confirmInfo} onCancel={handleCancel_confirmInfo}>
<Form
form={confirmInfo_form}
labelCol={{
span: 5,
}}>
<Form.Item label="确认信息" name="ConfirmInfo" rules={[{ required: true }]}>
<Input.TextArea rows={4} />
</Form.Item>
</Form>
</Modal>
<Modal
title="费用信息"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
okText="保存"
cancelText="关闭"
footer={(_, { OkBtn, CancelBtn }) => (
<>
<CancelBtn />
{isAddNew ? (
<>
<Button type="primary" onClick={() => handleOk(false)}>
添加并继续新增
</Button>{" "}
<Button type="primary" onClick={() => handleOk(true)}>
添加并关闭
</Button>
</>
) : (
<OkBtn />
)}
</>
)}>
<Form
form={ticket_form}
labelCol={{
span: 5,
}}>
<Form.Item label="状态" name="CostType">
<Select
style={{
width: 160,
}}
onChange={onChangeType}
options={[
{
value: "出票",
label: "出票",
},
{
value: "改签",
label: "改签",
},
{
value: "退票",
label: "退票",
},
]}
/>
</Form.Item>
{isTicketType && (
<>
<Form.Item label="PNR" name="PNR" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label="票号" name="TicketNo" rules={[{ required: true }]}>
<Input
style={{
width: 160,
}}
/>
</Form.Item>
<Form.Item label="机票类型" name="FlightType" rules={[{ required: true }]}>
<Select
style={{
width: 160,
}}
options={[
{
value: "成人",
label: "成人",
},
{
value: "儿童",
label: "儿童",
},
{
value: "婴儿",
label: "婴儿",
},
]}
/>
</Form.Item>
<Form.Item label="机票价格" name="Cost" rules={[{ required: true }]}>
<Input
placeholder="含基建和税"
prefix="¥"
style={{
width: 160,
}}
/>
</Form.Item>
</>
)}
<Form.Item label="服务费" name="ServiceFee" rules={[{ required: true }]}>
<Input
prefix="¥"
style={{
width: 160,
}}
/>
</Form.Item>
{isTicketType && (
<>
<Form.Item label="折扣" name="Discount" rules={[{ required: true }]}>
<Input
style={{
width: 160,
}}
placeholder="如 0.9"
/>
</Form.Item>
<Form.Item label="选择客人" name="MEI_Name66">
<Radio.Group
onChange={e => guestList_OnChange(e)}
style={{
minWidth: 320,
}}>
<List
bordered
dataSource={guestList_select()}
renderItem={item => (
<List.Item>
<Radio value={item.value}>{item.label}</Radio>
</List.Item>
)}></List>
</Radio.Group>
{/* <Select onChange={value => guestList_OnChange(value)} options={guestList_select()} placeholder="如果列表里面没有客人信息,请手动录到备注里" /> */}
</Form.Item>
</>
)}
<Form.Item label="客人信息/备注" name="Memo">
<Input.TextArea rows={4} disabled={isTicketType} />
</Form.Item>
<Form.Item name="CLF_SN" hidden>
<input />
</Form.Item>
<Form.Item name="GRI_SN" hidden>
<input />
</Form.Item>
<Form.Item name="VEI_SN" hidden>
<input />
</Form.Item>
<Form.Item name="CLC_SN" hidden>
<input />
</Form.Item>
</Form>
</Modal>
</Space>
);
};

@ -12,7 +12,7 @@ const { Title, Text, Paragraph } = Typography;
function Detail() {
const navigate = useNavigate();
const { GRI_SN, RefNo, CII_SN } = useParams();
const {travelAgencyId, token} = usingStorage();
const {travelAgencyId, loginToken} = usingStorage();
const desc = ['none', 'Unacceptable', 'Poor', 'Fair', 'Very Good', 'Excellent'];
const { notification } = App.useApp();
const [form] = Form.useForm();
@ -168,7 +168,7 @@ function Detail() {
name='ghhfile'
// accept="image/*"
multiple={true}
action={config.HT_HOST + `/service-fileServer/FileUpload?GRI_SN=${GRI_SN}&VEI_SN=${travelAgencyId}&token=${token}`}
action={config.HT_HOST + `/service-fileServer/FileUpload?GRI_SN=${GRI_SN}&VEI_SN=${travelAgencyId}&token=${loginToken}`}
fileList={fileList}
listType='picture-card'
onChange={handleChange}

@ -11,7 +11,7 @@ const { Title, Text, Paragraph } = Typography;
function Detail() {
const navigate = useNavigate();
const { GRI_SN,RefNo } = useParams();
const {travelAgencyId, token} = usingStorage();
const {travelAgencyId, loginToken} = usingStorage();
const desc = ["none", "Unacceptable", "Poor", "Fair", "Very Good", "Excellent"];
const { notification } = App.useApp();
const [form] = Form.useForm();
@ -166,7 +166,7 @@ function Detail() {
name="ghhfile"
// accept="image/*"
multiple={true}
action={config.HT_HOST + `/service-fileServer/FileUpload?GRI_SN=${GRI_SN}&VEI_SN=${travelAgencyId}&token=${token}`}
action={config.HT_HOST + `/service-fileServer/FileUpload?GRI_SN=${GRI_SN}&VEI_SN=${travelAgencyId}&token=${loginToken}`}
fileList={fileList}
listType="picture-card"
onChange={handleChange}

@ -1,21 +1,21 @@
import { useParams, useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
import { Row, Col, Space, Button, Typography, Card, Form, Upload, Input, Divider, DatePicker, Select, App, Descriptions, Image } from "antd";
import { PlusOutlined } from "@ant-design/icons";
import { PlusOutlined,AuditOutlined } from "@ant-design/icons";
import { isNotEmpty } from "@/utils/commons";
import * as config from "@/config";
import dayjs from "dayjs";
import { fetchInvoiceDetail, postEditInvoiceDetail, postAddInvoice } from '@/stores/Invoice';
import { removeFeedbackImages } from '@/stores/Feedback';
import BackBtn from '@/components/BackBtn';
import { fetchInvoiceDetail, postEditInvoiceDetail, postAddInvoice } from "@/stores/Invoice";
import { removeFeedbackImages } from "@/stores/Feedback";
import BackBtn from "@/components/BackBtn";
import { usingStorage } from "@/hooks/usingStorage";
const { Title,Text } = Typography;
const { Title, Text } = Typography;
function Detail() {
const navigate = useNavigate();
const { GMDSN, GSN } = useParams();
const {userId, travelAgencyId, token} = usingStorage();
const { userId, travelAgencyId, loginToken } = usingStorage();
const [form] = Form.useForm();
const [dataLoading, setDataLoading] = useState(false);
const [edited, setEdited] = useState(true); //
@ -24,13 +24,13 @@ function Detail() {
const [invoicePicList, setInvoicePicList] = useState([]);
const [invoiceZDDetail, setInvoiceZDDetail] = useState([]);
const [invoiceCurrencyList, setInvoiceCurrencyList] = useState([]);
const [invoiceProductList, setInvoiceProductList] = useState([]);
const [invoiceGroupInfo, setInvoiceGroupInfo] = useState({});
const [invoiceZDDetail, setInvoiceZDDetail] = useState([]);
const [invoiceCurrencyList, setInvoiceCurrencyList] = useState([]);
const [invoiceProductList, setInvoiceProductList] = useState([]);
const [invoiceGroupInfo, setInvoiceGroupInfo] = useState({});
const [invoiceFormData, setInvoiceFormData] = useState({});
const [invoicekImages, setInvoicekImages] = useState([]);
const [invoiceFormData, setInvoiceFormData] = useState({});
const [invoicekImages, setInvoicekImages] = useState([]);
useEffect(() => {
console.info("Detail.useEffect: " + GMDSN + "/" + GSN);
@ -40,12 +40,12 @@ function Detail() {
function defaultShow() {
setDataLoading(true);
fetchInvoiceDetail(travelAgencyId, GMDSN, GSN)
fetchInvoiceDetail(travelAgencyId, GMDSN, GSN)
.then(json => {
setInvoiceZDDetail(json.invoiceZDDetail);
setInvoiceGroupInfo(json.invoiceGroupInfo);
setInvoiceProductList(json.invoiceProductList);
setInvoiceCurrencyList(json.invoiceCurrencyList);
setInvoiceZDDetail(json.invoiceZDDetail);
setInvoiceGroupInfo(json.invoiceGroupInfo);
setInvoiceProductList(json.invoiceProductList);
setInvoiceCurrencyList(json.invoiceCurrencyList);
let ZDDetail = json.invoiceZDDetail;
if (isNotEmpty(ZDDetail)) {
@ -53,8 +53,8 @@ function Detail() {
const formData = ZDDetail.map((data, index) => {
if (data.GMD_Dealed == false && arrLen == index + 1) {
//
const _formData = { info_money: data.GMD_Cost, info_Currency: data.GMD_Currency, info_date: isNotEmpty(data.GMD_PayDate) ? dayjs(data.GMD_PayDate) : "", info_gmdsn: data.GMD_SN };
setInvoiceFormData(_formData);
const _formData = { info_money: data.GMD_Cost, info_Currency: data.GMD_Currency, info_date: isNotEmpty(data.GMD_PayDate) ? dayjs(data.GMD_PayDate) : "", info_gmdsn: data.GMD_SN };
setInvoiceFormData(_formData);
return _formData;
}
});
@ -79,7 +79,7 @@ function Detail() {
});
}
if (data.GMD_Dealed == false && arrLen == index + 1) {
setInvoicekImages(picList);
setInvoicekImages(picList);
}
return picList;
});
@ -99,7 +99,7 @@ function Detail() {
});
}
const fileList = (invoicekImages);
const fileList = invoicekImages;
//
let arrimg = [];
if (isNotEmpty(fileList)) {
@ -120,8 +120,8 @@ function Detail() {
if (fieldVaule) {
postEditInvoiceDetail(userId, fieldVaule.info_gmdsn, fieldVaule.info_Currency, fieldVaule.info_money, fieldVaule.info_date, fieldVaule.info_images, "").then(data => {
// console.log(data);
let param = { info_money: fieldVaule.info_money, info_Currency: fieldVaule.info_Currency, info_date: fieldVaule.info_date };
setInvoiceFormData(param);
let param = { info_money: fieldVaule.info_money, info_Currency: fieldVaule.info_Currency, info_date: fieldVaule.info_date };
setInvoiceFormData(param);
if (data.errcode == 0) {
setEdited(true);
notification.success({
@ -144,7 +144,7 @@ function Detail() {
}
return file;
});
setInvoicekImages(newFileList);
setInvoicekImages(newFileList);
};
const handRemove = info => {
@ -167,7 +167,7 @@ function Detail() {
}
function addInvoice() {
postAddInvoice(userId, travelAgencyId, GSN, "", 0, "", "[]", "")
postAddInvoice(userId, travelAgencyId, GSN, "", 0, "", "[]", "")
.then(data => {})
.finally(() => {
defaultShow();
@ -193,20 +193,20 @@ function Detail() {
}
}
const invoiceStatus = (FKState) => {
const invoiceStatus = FKState => {
switch (FKState - 1) {
case 1:
return 'Submitted';
case 2:
return 'Travel Advisor';
case 3:
return 'Finance Dept';
case 4:
return 'Paid';
default:
return '';
}
};
case 1:
return "Submitted";
case 2:
return "Travel Advisor";
case 3:
return "Finance Dept";
case 4:
return "Paid";
default:
return "";
}
};
//
function bindSubmitForm() {
@ -218,13 +218,13 @@ function Detail() {
<Row key={data.GMD_SN} gutter={16}>
<Col span={4}></Col>
<Col span={16}>
<Card type="inner" title={"Invoice " + ++index}>
<Card type="inner" title={"Invoice " + (index + 1)}>
<Row gutter={16}>
<Col span={12}>
<Descriptions column={1}>
<Descriptions.Item label="Amount">{data.GMD_Cost}</Descriptions.Item>
<Descriptions.Item label="Currency">{data.GMD_Currency}</Descriptions.Item>
<Descriptions.Item label="Due Dat">{data.GMD_PayDate}</Descriptions.Item>
<Descriptions.Item label="Due Date">{data.GMD_PayDate}</Descriptions.Item>
<Descriptions.Item label="Status">{invoiceStatus(data.FKState)}</Descriptions.Item>
</Descriptions>
</Col>
@ -232,13 +232,15 @@ function Detail() {
<Image.PreviewGroup>
{invoicePicList[index] &&
invoicePicList[index].map(item => {
return <Image key={item.uid} width={90} src={item.url} />;
if (item.url) {
return <Image key={item.uid} width={90} src={item.url} />;
}
})}
</Image.PreviewGroup>
</Col>
</Row>
</Card>
{addButton(index++ == invoiceZDDetail.length)}
{addButton(index + 1 == invoiceZDDetail.length)}
</Col>
<Col span={4}></Col>
</Row>
@ -251,7 +253,7 @@ function Detail() {
<Col span={16}>
<Card
type="inner"
title={"Invoice " + ++index}
title={"Invoice " + (index + 1)}
extra={
<Button type="link" onClick={() => setEdited(false)}>
Edit
@ -283,21 +285,25 @@ function Detail() {
]}>
<Select placeholder="Select Currency type" onChange={onCurrencyChange} options={bindCurrency()}></Select>
</Form.Item>
<Form.Item name="info_date" label="Due Month"
<Form.Item
name="info_date"
label="Due Month"
rules={[
{
required:true,
message:"please select Due Month!",
required: true,
message: "please select Due Month!",
},
]}
>
]}>
<DatePicker picker="month" />
</Form.Item>
<Text type="secondary">Payment is arranged during the last week of each month. If the invoice is issued after the 20th, please select the following month for payment. For urgent payments, please contact the travel advisor. </Text>
<Text type="secondary">
Payment is arranged during the last week of each month. If the invoice is issued after the 20th, please select the following month for payment. For urgent payments, please contact the
travel advisor.{" "}
</Text>
<Form.Item name="info_gmdsn" hidden={true}>
<input />
</Form.Item>
<br/>
<br />
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
@ -309,13 +315,12 @@ function Detail() {
<Upload
name="ghhfile"
multiple={true}
action={config.HT_HOST + `/service-fileServer/FileUpload?GRI_SN=${GSN}&VEI_SN=${travelAgencyId}&FilePathName=invoice&token=${token}`}
action={config.OVERSEA_HOST + `/service-fileServer/FileUpload?GRI_SN=${GSN}&VEI_SN=${travelAgencyId}&FilePathName=invoice&token=${loginToken}`}
fileList={fileList}
listType="picture-card"
onChange={handleChange}
onRemove={handRemove}
accept=".jpg,.png,.peg,.bmp"
>
accept=".jpg,.png,.peg,.bmp">
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload Invoice</div>
@ -352,7 +357,10 @@ function Detail() {
<Title level={4}>Reference Number: {invoiceGroupInfo.VGroupInfo}</Title>
</Col>
<Col span={4}>
<BackBtn />
<BackBtn />
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/history/0/338787`)}>
Billing Records
</Button>
</Col>
</Row>
<Title level={5}></Title>
@ -362,4 +370,4 @@ function Detail() {
);
}
export default (Detail);
export default Detail;

@ -0,0 +1,130 @@
import { useParams, NavLink, useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
import { Row, Col, Space, Table, Image,App } from "antd";
import { formatDate, isNotEmpty } from "@/utils/commons";
import SearchForm from "@/components/SearchForm";
import dayjs from "dayjs";
import BackBtn from "@/components/BackBtn";
import { fetchInvoiceDetail } from "@/stores/Invoice";
import useInvoiceStore from "@/stores/Invoice";
import { usingStorage } from "@/hooks/usingStorage";
function History() {
const { travelAgencyId } = usingStorage();
const { GMDSN, GSN } = useParams();
const [dataLoading, setDataLoading] = useState(false);
const [invoiceZDDetail, setInvoiceZDDetail] = useState([]);
const { notification } = App.useApp();
useEffect(() => {
defaultShow();
}, [GMDSN, GSN]);
function defaultShow() {
setDataLoading(true);
fetchInvoiceDetail(travelAgencyId, GMDSN, GSN)
.then((json) => {
//console.log("id:"+travelAgencyId+",gmdsn:"+GMDSN+",GSN:"+GSN+"#13"+json);
setInvoiceZDDetail(json.invoiceZDDetail);
})
.catch((ex) => {
notification.error({
message: `Notification`,
description: ex.message,
placement: "top",
duration: 4,
});
})
.finally(() => {
setDataLoading(false);
});
}
const invoicePaidColumns = [
{
title: "ID",
dataIndex: "GMD_SN",
key: "GMD_SN",
},
{
title: "Due Date",
key: "GMD_PayDate",
dataIndex: "GMD_PayDate",
render: (text, record) =>
isNotEmpty(text) ? formatDate(new Date(text)) : "",
},
{
title: "Amount",
key: "GMD_Cost",
dataIndex: "GMD_Cost",
},
{
title: "Currency",
key: "GMD_Currency",
dataIndex: "GMD_Currency",
},
{
title: "Status",
key: "FKState",
dataIndex: "FKState",
render: (text, record) => (isNotEmpty(record.FKState) ? invoiceStatus(record.FKState) : text),
},
{
title: "Invoice",
key: "GMD_Pic",
dataIndex: "GMD_Pic",
render: showPIc,
},
];
const invoiceStatus = (FKState) => {
switch (FKState - 1) {
case 1:
return "Submitted";
case 2:
return "Travel Advisor";
case 3:
return "Finance Dept";
case 4:
return "Paid";
default:
return "";
}
};
function showPIc(text, record) {
let strPic = record.GMD_Pic;
//console.log(JSON.parse(strPic));
if (isNotEmpty(strPic)) {
return JSON.parse(strPic).map((item, index) => {
return <Image key={index} width={90} src={item.url} />;
});
} else {
return "";
}
}
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row gutter={16}>
<Col span={20}></Col>
<Col span={4}>
<BackBtn />
</Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24}>
<Table
rowKey={"GMD_SN"}
bordered
columns={invoicePaidColumns}
dataSource={invoiceZDDetail}
/>
</Col>
</Row>
</Space>
);
}
export default History;

@ -72,33 +72,35 @@ function Index() {
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row gutter={16}>
<Col flex="auto">
<SearchForm
initialValue={{
dates: [dayjs().subtract(2, 'M').startOf('M'), dayjs().endOf('M')],
}}
fieldsConfig={{
shows: ['referenceNo', 'invoiceStatus', 'dates'],
fieldProps: {
referenceNo: { col: 7 },
invoiceStatus: { col: 4},
dates: { col: 10 },
},
}}
onSubmit={(err, formVal, filedsVal) => {
fetchInvoiceList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.enddate, formVal.invoiceStatus);
}}
/>
</Col>
<Col md={24} lg={4} xxl={4}>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/detail/0/338787`)}>
Misc. Invoice
</Button>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/paid`)}>
Bank statement
</Button>
</Col>
</Row>
<Col md={16} sm={16} xs={24} >
<SearchForm
initialValue={{
dates: [dayjs().subtract(2, 'M').startOf('M'), dayjs().endOf('M')],
}}
fieldsConfig={{
shows: ['referenceNo', 'invoiceStatus', 'dates'],
fieldProps: {
referenceNo: { col: 7 },
invoiceStatus: { col: 4},
dates: { col: 10 },
},
}}
onSubmit={(err, formVal, filedsVal) => {
fetchInvoiceList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.enddate, formVal.invoiceStatus);
}}
/>
</Col>
<Col md={8} sm={8} xs={24} >
<Space>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/detail/0/395074`)}>
Misc. Invoice
</Button>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/paid`)}>
Bank statement
</Button>
</Space>
</Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24}>
<Table bordered pagination={{ defaultPageSize: 20, showTotal: showTotal }} columns={invoiceListColumns} dataSource={(invoiceList)} />

@ -43,11 +43,11 @@ function Detail() {
backTo={false}
header={<Header title={activeAgency.travel_agency_name} refresh={handleGetAgencyProducts} handleNewProduct={() => setAddProductVisible(true)} />}>
<>
<Flex gap={10}>
<Flex gap={10} className='h-full'>
{/* onNodeSelect={handleNodeSelect} */}
<ProductsTree className='basis-80 sticky top-16 overflow-y-auto shrink-0' style={{ height: 'calc(100vh - 150px)' }} />
<ProductsTree className='basis-80 sticky top-0 overflow-y-auto shrink-0' style1={{ height: 'calc(100vh - 150px)' }} />
<Divider type={'vertical'} className='mx-1 h-auto' />
<div className=' flex-auto '>
<div className=' flex-auto overflow-auto '>
<ProductInfo />
</div>
</Flex>

@ -0,0 +1,100 @@
import { useState } from 'react'
import { Form, Modal, Input, Button, Flex, App } from 'antd'
import { useTranslation } from 'react-i18next'
import useProductsStore from '@/stores/Products/Index'
import { useProductsTypesMapVal } from '@/hooks/useProductsSets'
import RequireAuth from '@/components/RequireAuth'
import { PERM_PRODUCTS_OFFER_PUT } from '@/config'
export const ContractRemarksModal = () => {
const { t } = useTranslation()
const { notification } = App.useApp()
const productsTypesMapVal = useProductsTypesMapVal()
const [getRemarkList, saveOrUpdateRemark] = useProductsStore((state) => [
state.getRemarkList, state.saveOrUpdateRemark
])
const [isRemarksModalOpen, setRemarksModalOpen] = useState(false)
const [remarksForm] = Form.useForm()
const onRemarksFinish = () => {
const remarkList = remarksForm.getFieldsValue().remarkList
saveOrUpdateRemark(remarkList)
.then(() => {
setRemarksModalOpen(false)
notification.info({
message: 'Notification',
description: '合同备注保存成功',
placement: 'top',
})
})
.catch(ex => {
notification.error({
message: 'Notification',
description: ex.message,
placement: 'top',
})
})
}
const handleContractRemarks = () => {
getRemarkList()
.then(list => {
remarksForm.setFieldsValue({remarkList:list})
setRemarksModalOpen(true)
})
.catch(ex => {
notification.error({
message: 'Notification',
description: ex.message,
placement: 'top',
})
})
}
const getFieldLabel = (field) => {
const remarkList = remarksForm.getFieldsValue([['remarkList']]).remarkList
return productsTypesMapVal[remarkList[field.key].product_type_id]?.label
}
return (
<>
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<Button size='small' onClick={handleContractRemarks}>{t('products:ContractRemarks')}</Button>
</RequireAuth>
<Modal
centered
title={t('products:ContractRemarks')}
width={'640px'}
open={isRemarksModalOpen}
onOk={() => onRemarksFinish()}
onCancel={() => setRemarksModalOpen(false)}
destroyOnClose
forceRender
>
<Form
labelCol={{ span: 3 }}
wrapperCol={{ span: 20 }}
form={remarksForm}
name='remarksForm'
autoComplete='off'
>
<Form.List name='remarkList'>
{(fields) => (
<Flex gap='middle' vertical>
{fields.map((field) => (
<Form.Item label={getFieldLabel(field)} name={[field.name, 'Memo']} key={field.key}>
<Input.TextArea rows={2}></Input.TextArea>
</Form.Item>
))}
</Flex>
)}
</Form.List>
</Form>
</Modal>
</>
);
};
export default ContractRemarksModal

@ -10,12 +10,15 @@ import RequireAuth from '@/components/RequireAuth';
import { PERM_PRODUCTS_MANAGEMENT } from '@/config';
import { useProductsTypesMapVal } from '@/hooks/useProductsSets';
import { usingStorage } from '@/hooks/usingStorage';
import useProductsStore from '@/stores/Products/Index';
const NewAddonModal = ({ onPick, ...props }) => {
const { travel_agency_id, use_year } = useParams();
// const { travel_agency_id, use_year } = useParams();
const { t } = useTranslation();
const { notification, message } = App.useApp();
const [{ travel_agency_id, use_year }] = useProductsStore((state) => [state.switchParams]);
const productsTypesMapVal = useProductsTypesMapVal();
const [open, setOpen] = useState(false);
@ -29,7 +32,7 @@ const NewAddonModal = ({ onPick, ...props }) => {
setSearchLoading(true);
setSearchResult([]);
const search_year = year || use_year;
const result = await searchPublishedProductsAction({ ...param, use_year: search_year, travel_agency_id, });
const result = await searchPublishedProductsAction({ ...param, use_year: search_year, travel_agency_id });
setSearchResult(result);
setSearchLoading(false);
};
@ -110,8 +113,9 @@ const Extras = ({ productId, onChange, ...props }) => {
const { t } = useTranslation();
const { notification, message } = App.useApp();
const { travel_agency_id, use_year } = useParams();
// const { travel_agency_id, use_year } = useParams();
const { travelAgencyId } = usingStorage();
const [{travel_agency_id, use_year}] = useProductsStore((state) => [state.switchParams]);
const [extrasData, setExtrasData] = useState([]);
@ -132,7 +136,7 @@ const Extras = ({ productId, onChange, ...props }) => {
}
const handleDelAddon = async (item) => {
const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, extras: [item.id] });
const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, del_extras_ids: [item.id] });
delSuccess ? message.success(`${t('Success')}`) : message.error(`${t('Failed')}`);
await handleGetAgencyProductExtras();
};
@ -150,7 +154,7 @@ const Extras = ({ productId, onChange, ...props }) => {
dataIndex: 'operation',
width: '4rem',
render: (_, r) => (
<Popconfirm title={t('sureDelete')} onConfirm={(e) => handleDelAddon(r)} okText={t('Yes')} >
<Popconfirm title={t('sureDelete')} onConfirm={(e) => handleDelAddon(r.info)} okText={t('Yes')} >
<Button size='small' type='link' danger>
{t('Delete')}
</Button>

@ -1,34 +1,46 @@
import { useEffect, useState } from 'react';
import { useParams, Link, useNavigate, useLocation } from 'react-router-dom';
import { App, Button, Divider, Popconfirm, Select } from 'antd';
import { ReloadOutlined } from '@ant-design/icons';
import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import { useTranslation } from 'react-i18next';
import useProductsStore, { postAgencyProductsAuditAction, postAgencyAuditAction, getAgencyAllExtrasAction } from '@/stores/Products/Index';
import { isEmpty, objectMapper } from '@/utils/commons';
import useAuthStore from '@/stores/Auth';
import RequireAuth from '@/components/RequireAuth';
import { useEffect, useState } from "react";
import { useParams, Link, useNavigate, useLocation } from "react-router-dom";
import { App, Button, Divider, Popconfirm, Select } from "antd";
import { ReloadOutlined } from "@ant-design/icons";
import { useProductsAuditStatesMapVal } from "@/hooks/useProductsSets";
import { useTranslation } from "react-i18next";
import useProductsStore, {
postAgencyProductsAuditAction,
postAgencyAuditAction,
getAgencyAllExtrasAction,
} from "@/stores/Products/Index";
import { isEmpty, objectMapper } from "@/utils/commons";
import useAuthStore from "@/stores/Auth";
import RequireAuth from "@/components/RequireAuth";
// import PrintContractPDF from './PrintContractPDF';
import { PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config';
import dayjs from 'dayjs';
import VendorSelector from '@/components/VendorSelector';
import AuditStateSelector from '@/components/AuditStateSelector';
import NewProductModal from './NewProductModal';
import { PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from "@/config";
import dayjs from "dayjs";
import VendorSelector from "@/components/VendorSelector";
import AuditStateSelector from "@/components/AuditStateSelector";
import { usingStorage } from "@/hooks/usingStorage";
import AgencyContract from './../Print/AgencyContract';
import AgencyContract from "../Print/AgencyContract";
// import AgencyContract from "../Print/AgencyContract_v0903";
import { saveAs } from "file-saver";
import { Packer } from "docx";
const Header = ({ refresh, ...props }) => {
const location = useLocation();
const isEditPage = location.pathname.includes('edit');
const showEditA = !location.pathname.includes('edit');
const showAuditA = !location.pathname.includes('audit');
const isEditPage = location.pathname.includes("edit");
const showEditA = !location.pathname.includes("edit");
const showAuditA = !location.pathname.includes("audit");
const { travel_agency_id, use_year, audit_state } = useParams();
const { travelAgencyId } = usingStorage();
const { t } = useTranslation();
const isPermitted = useAuthStore((state) => state.isPermitted);
const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]);
const [switchParams, setSwitchParams] = useProductsStore((state) => [state.switchParams, state.setSwitchParams]);
const [activeAgency, setActiveAgency] = useProductsStore((state) => [
state.activeAgency,
state.setActiveAgency,
]);
const [switchParams, setSwitchParams] = useProductsStore((state) => [
state.switchParams,
state.setSwitchParams,
]);
// const [activeAgencyState] = useProductsStore((state) => [state.activeAgencyState]);
const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]);
const stateMapVal = useProductsAuditStatesMapVal();
@ -37,26 +49,49 @@ const Header = ({ refresh, ...props }) => {
const yearOptions = [];
const currentYear = switchParams.use_year || dayjs().year();
const baseYear = use_year ? Number(use_year === 'all' ? currentYear : use_year) : currentYear;
const baseYear = use_year
? Number(use_year === "all" ? currentYear : use_year)
: currentYear;
for (let i = currentYear - 5; i <= baseYear + 5; i++) {
yearOptions.push({ label: i, value: i });
}
const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id });
const { getRemarkList } = useProductsStore((selector) => ({
getRemarkList: selector.getRemarkList,
}));
const [param, setParam] = useState({
pick_year: baseYear,
pick_agency: travel_agency_id,
});
const [pickYear, setPickYear] = useState(baseYear);
const [pickAgency, setPickAgency] = useState({ value: activeAgency.travel_agency_id, label: activeAgency.travel_agency_name });
const [pickAgency, setPickAgency] = useState({
value: activeAgency.travel_agency_id,
label: activeAgency.travel_agency_name,
});
const [pickAuditState, setPickAuditState] = useState();
useEffect(() => {
const _param = objectMapper(param, { pick_year: 'use_year', pick_agency: 'travel_agency_id', pick_state: 'audit_state' });
setSwitchParams({ ..._param });
const _param = objectMapper(param, {
pick_year: "use_year",
pick_agency: "travel_agency_id",
pick_state: "audit_state",
});
setSwitchParams({
..._param,
travel_agency_id: _param?.travel_agency_id || travelAgencyId,
});
refresh(param);
return () => {};
}, [param]);
const emptyPickState = { value: '', label: t('products:State') };
const emptyPickState = { value: "", label: t("products:State") };
useEffect(() => {
const baseState = audit_state ? (audit_state === 'all' ? emptyPickState : stateMapVal[`${audit_state}`]) : emptyPickState;
const baseState = audit_state
? audit_state === "all"
? emptyPickState
: stateMapVal[`${audit_state}`]
: emptyPickState;
if (isEmpty(pickAuditState)) {
setPickAuditState(baseState);
}
@ -83,20 +118,23 @@ const Header = ({ refresh, ...props }) => {
// const s = Object.keys(agencyProducts).map((typeKey) => {
// });
postAgencyProductsAuditAction(state, { travel_agency_id: activeAgency.travel_agency_id, use_year: switchParams.use_year })
postAgencyProductsAuditAction(state, {
travel_agency_id: activeAgency.travel_agency_id,
use_year: switchParams.use_year,
})
.then((json) => {
if (json.errcode === 0) {
message.success(json.errmsg);
if (typeof refresh === 'function') {
if (typeof refresh === "function") {
refresh(param);
}
}
})
.catch((ex) => {
notification.error({
message: 'Notification',
message: "Notification",
description: ex.message,
placement: 'top',
placement: "top",
duration: 4,
});
});
@ -106,112 +144,168 @@ const Header = ({ refresh, ...props }) => {
postAgencyAuditAction(activeAgency.travel_agency_id, switchParams.use_year)
.then((json) => {
if (json.errcode === 0) {
message.success(t('Success'));
if (typeof refresh === 'function') {
message.success(t("Success"));
if (typeof refresh === "function") {
refresh(param);
const auditPagePath = isPermitted(PERM_PRODUCTS_OFFER_AUDIT)
? `/products/${activeAgency.travel_agency_id}/${switchParams.use_year}/all/audit`
: isPermitted(PERM_PRODUCTS_OFFER_PUT)
? `/products/audit`
: '';
: "";
navigate(auditPagePath);
}
}
})
.catch((ex) => {
notification.error({
message: 'Notification',
message: "Notification",
description: ex.message,
placement: 'top',
placement: "top",
duration: 4,
});
});
};
const handleDownload = async () => {
// await refresh();
const agencyExtras = await getAgencyAllExtrasAction(switchParams);
const remarks = await getRemarkList()
const documentCreator = new AgencyContract();
const doc = documentCreator.create([switchParams, activeAgency, agencyProducts, agencyExtras]);
const doc = documentCreator.create([
switchParams,
activeAgency,
agencyProducts,
agencyExtras,
remarks
]);
const _d = dayjs().format('YYYYMMDD_HH.mm.ss.SSS'); // Date.now().toString(32)
Packer.toBlob(doc).then(blob => {
saveAs(blob, `${activeAgency.travel_agency_name}${pickYear}年地接合同-${_d}.docx`);
const _d = dayjs().format("YYYYMMDD_HH.mm.ss.SSS"); // Date.now().toString(32)
Packer.toBlob(doc).then((blob) => {
saveAs(
blob,
`${activeAgency.travel_agency_name}${pickYear}年地接合同-${_d}.docx`
);
});
};
return (
<div className='flex justify-end items-center gap-4 h-full'>
<div className='grow'>
<h2 className='m-0 leading-tight'>
<div className="flex justify-end items-center gap-4 h-full">
<div className="grow">
<h2 className="m-0 leading-tight">
{isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? (
<VendorSelector
value={{ label: activeAgency.travel_agency_name, value: activeAgency.travel_agency_id }}
value={{
label: activeAgency.travel_agency_name,
value: activeAgency.travel_agency_id,
}}
onChange={handleAgencyChange}
allowClear={false}
mode={null}
className='w-72'
size='large'
variant={'borderless'}
className="w-72"
size="large"
variant={"borderless"}
/>
) : (
activeAgency.travel_agency_name
)}
<Divider type={'vertical'} />
<Select options={yearOptions} variant={'borderless'} className='w-24' size='large' value={pickYear} onChange={handleYearChange} />
<Divider type={'vertical'} />
<AuditStateSelector variant={'borderless'} className='w-32' size='large' value={pickAuditState} onChange={handleAuditStateChange} />
<Divider type={"vertical"} />
<Select
options={yearOptions}
variant={"borderless"}
className="w-24"
size="large"
value={pickYear}
onChange={handleYearChange}
/>
<Divider type={"vertical"} />
<AuditStateSelector
variant={"borderless"}
className="w-32"
size="large"
value={pickAuditState}
onChange={handleAuditStateChange}
/>
{/* <Divider type={'vertical'} />
{(use_year || '').replace('all', '')} */}
<Button onClick={() => refresh(param)} type='text' className='text-primary round-none' icon={<ReloadOutlined />} />
<Button
onClick={() => refresh(param)}
type="text"
className="text-primary round-none"
icon={<ReloadOutlined />}
/>
</h2>
</div>
{/* todo: export, 审核完成之后才能导出 */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size="small" onClick={handleDownload}>
{t("Export")} .docx
</Button>
{/* <PrintContractPDF /> */}
</RequireAuth>
{/* {activeAgencyState === 0 && ( */}
<>
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<Popconfirm
title={t("products:sureSubmitAudit")}
onConfirm={handleSubmitForAudit}
okText={t("Yes")}
placement={"bottomLeft"}
>
<Button size="small" type={"primary"}>
{t("Submit")}
{t("Audit")}
</Button>
</Popconfirm>
</RequireAuth>
</>
{/* )} */}
{showEditA && (
<Link className='px-2' to={isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? `/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/edit` : `/products/edit`}>
{t('Edit')}
<Link
className="px-2"
to={
isPermitted(PERM_PRODUCTS_OFFER_AUDIT)
? `/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/edit`
: `/products/edit`
}
>
{t("Edit")}
</Link>
)}
{showAuditA && (
<Link className='px-2' to={isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? `/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/audit` : `/products/audit`}>
{t('products:AuditRes')}
<Link
className="px-2"
to={
isPermitted(PERM_PRODUCTS_OFFER_AUDIT)
? `/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/audit`
: `/products/audit`
}
>
{t("products:AuditRes")}
</Link>
)}
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' type={'primary'} onClick={() => handleAuditAgency('1')}>
{t('products:auditStateAction.Published')}
<Button
size="small"
type={"primary"}
onClick={() => handleAuditAgency("1")}
>
{t("products:auditStateAction.Published")}
</Button>
</RequireAuth>
{/* <Button size='small' type={'primary'} ghost onClick={() => handleAuditAgency('2')}>
{t('products:auditStateAction.Approved')}
</Button> */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' type={'primary'} danger ghost onClick={() => handleAuditAgency('3')}>
{t('products:auditStateAction.Rejected')}
<Button
size="small"
type={"primary"}
danger
ghost
onClick={() => handleAuditAgency("3")}
>
{t("products:auditStateAction.Rejected")}
</Button>
</RequireAuth>
{/* todo: export, 审核完成之后才能导出 */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' onClick={handleDownload}>{t('Export')} .docx</Button>
{/* <PrintContractPDF /> */}
</RequireAuth>
{/* 编辑 */}
<Divider type='vertical' />
{isEditPage && (
<NewProductModal />
)}
{/* {activeAgencyState === 0 && ( */}
<>
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<Popconfirm title={t('products:sureSubmitAudit')} onConfirm={handleSubmitForAudit} okText={t('Yes')} placement={'bottomLeft'}>
<Button size='small' type={'primary'} >
{t('Submit')}
{t('Audit')}
</Button>
</Popconfirm>
</RequireAuth>
</>
{/* )} */}
</div>
);
};

@ -8,7 +8,7 @@ import useProductsStore from '@/stores/Products/Index';
import { useNewProductRecord, useProductsTypesMapVal } from '@/hooks/useProductsSets';
import { useDefaultLgc } from '@/i18n/LanguageSwitcher';
import RequireAuth from '@/components/RequireAuth';
import { PERM_PRODUCTS_OFFER_PUT } from '@/config';
import { PERM_PRODUCTS_OFFER_PUT, PERM_PRODUCTS_NEW } from '@/config';
export const NewProductsForm = ({ initialValues, onFormInstanceReady, ...props }) => {
const { t } = useTranslation('products');
@ -91,7 +91,7 @@ export const NewProductModal = ({ initialValues }) => {
};
return (
<>
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<RequireAuth subject={PERM_PRODUCTS_NEW}>
<Button size='small' type={'primary'} onClick={() => setOpen(true)}>
{t('New')}
{t('products:#')}

@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useProductsTypesMapVal, useNewProductRecord } from '@/hooks/useProductsSets';
import useProductsStore, { postProductsSaveAction } from '@/stores/Products/Index';
import useAuthStore from '@/stores/Auth';
import { PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_PUT } from '@/config';
import { PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_PUT, PERM_PRODUCTS_INFO_PUT, PERM_PRODUCTS_NEW } from '@/config';
import { isEmpty, pick } from '@/utils/commons';
import ProductInfoForm from './ProductInfoForm';
import { usingStorage } from '@/hooks/usingStorage';
@ -31,7 +31,7 @@ const ProductInfo = ({ ...props }) => {
const hasHT = (editingProduct?.info?.htid || 0) > 0;
// const hasAuditPer = isPermitted(PERM_PRODUCTS_OFFER_AUDIT);
const hasEditPer = isPermitted(PERM_PRODUCTS_OFFER_PUT);
const hasEditPer = isPermitted(PERM_PRODUCTS_INFO_PUT) || isPermitted(PERM_PRODUCTS_NEW); // || isPermitted(PERM_PRODUCTS_OFFER_PUT);
setEditablePerm(topPerm || hasEditPer);
// setEditable(topPerm || (hasAuditPer ? true : (!hasHT && hasEditPer)));
@ -40,14 +40,35 @@ const ProductInfo = ({ ...props }) => {
setInfoEditable(topPerm || (!hasHT && hasEditPer));
const _priceEditable = [-1, 3].includes(activeAgency?.audit_state_id) || isEmpty(editingProduct?.info?.id);
// setPriceEditable(topPerm || (_priceEditable && hasEditPer));
setPriceEditable(true); // debug: 0
const hasPricePer = isPermitted(PERM_PRODUCTS_OFFER_PUT);
// setPriceEditable(topPerm || (_priceEditable && hasPricePer));
setPriceEditable(topPerm || (hasPricePer));
// setPriceEditable(true); // debug: 0
const showExtras = topPerm && hasHT; // !isEmpty(editingProduct) &&
setExtrasVisible(showExtras);
setLgcEdits({});
setInfoEditStatus('');
return () => {};
}, [activeAgency, editingProduct]);
const [infoEditStatus, setInfoEditStatus] = useState('');
const [lgcEdits, setLgcEdits] = useState({});
const onValuesChange = (changedValues, forms) => {
// console.log('onValuesChange', changedValues);
if ('product_title' in changedValues) {
setInfoEditStatus('2');
setLgcEdits({...lgcEdits, '2': {'edit_status': '2'}});
}
if ('lgc_details_mapped' in changedValues) {
const lgc = Object.keys(changedValues.lgc_details_mapped)[0];
setLgcEdits({...lgcEdits, [lgc]: {'edit_status': '2'}});
} else {
setInfoEditStatus('2');
}
};
const onSave = async (err, values, forms) => {
values.travel_agency_id = activeAgency.travel_agency_id;
const copyNewProduct = structuredClone(newProductRecord);
@ -58,6 +79,7 @@ const ProductInfo = ({ ...props }) => {
'travel_agency_id': activeAgency.travel_agency_id,
// "travel_agency_name": "",
// "lastedit_changed": "",
"edit_status": infoEditStatus || editingProduct.info.edit_status,
};
const copyFields = pick(editingProduct.info, ['product_type_id']); // 'title',
const readyToSubInfo = { ...copyNewProduct.info, ...editingProduct.info, title: editingProduct.info.product_title, ...values.info, ...copyFields, ...poster };
@ -65,9 +87,15 @@ const ProductInfo = ({ ...props }) => {
/** lgc_details */
const prevLgcDetailsMapped = editingProduct.lgc_details.reduce((r, c) => ({ ...r, [c.lgc]: c }), {});
const mergedLgc = { ...prevLgcDetailsMapped, ...values.lgc_details_mapped };
const mergedLgc = { ...prevLgcDetailsMapped, ...values.lgc_details_mapped, };
for (const lgcKey in lgcEdits) {
if (Object.prototype.hasOwnProperty.call(lgcEdits, lgcKey)) {
const element = lgcEdits[lgcKey];
mergedLgc[lgcKey].edit_status = element?.edit_status || values.lgc_details_mapped[lgcKey]?.edit_status || '2';
}
}
// console.log(values);
// console.log('before save', '\n lgcEdits:', lgcEdits, '\n mergedLgc', mergedLgc);
// return false; // debug: 0
/** 提交保存 */
setLoading(true);
@ -89,6 +117,7 @@ const ProductInfo = ({ ...props }) => {
success ? message.success(t('Success')) : message.error(t('Failed'));
//
// result.quotation = isEmpty(result.quotation) ? editingProduct.quotation : result.quotation;
result.info.htid = editingProduct?.info?.htid;
appendNewProduct(result);
setEditingProduct(result);
};
@ -110,7 +139,7 @@ const ProductInfo = ({ ...props }) => {
) : (
<>
<h2>{t('products:EditComponents.info')}</h2>
<ProductInfoForm {...{ editablePerm, infoEditable, priceEditable }} initialValues={editingProduct?.info} onSubmit={onSave} />
<ProductInfoForm {...{ editablePerm, infoEditable, priceEditable, onValuesChange }} initialValues={editingProduct?.info} onSubmit={onSave} />
<Divider className='my-1' />
{extrasVisible && <Extras productId={editingProduct?.info?.id} />}
</>

@ -29,6 +29,8 @@ const InfoForm = ({ onSubmit, onReset, onValuesChange, editablePerm, infoEditabl
const filedsets = useProductsTypesFieldsets(editingProduct?.info?.product_type_id);
const shows = filedsets[0];
const [pickEditedInfo, setPickEditedInfo] = useState({}); //
// const [editable, setEditable] = useState(true);
const [formEditable, setFormEditable] = useState(true);
const [showSave, setShowSave] = useState(true);
@ -39,6 +41,8 @@ const InfoForm = ({ onSubmit, onReset, onValuesChange, editablePerm, infoEditabl
const lgc_details_mapped = (editingProduct?.lgc_details || []).reduce((r, c) => ({ ...r, [c.lgc]: c }), {});
form.setFieldValue('lgc_details_mapped', lgc_details_mapped);
form.setFieldValue('quotation', editingProduct?.quotation);
form.setFieldValue('display_to_c', editingProduct.info?.display_to_c || '0');
setPickEditedInfo({ ...pickEditedInfo, product_title: editingProduct?.info?.product_title });
setFormEditable(infoEditable || priceEditable);
@ -46,7 +50,7 @@ const InfoForm = ({ onSubmit, onReset, onValuesChange, editablePerm, infoEditabl
setShowSave(infoEditable || priceEditable);
// setEditable(editable0);
return () => {};
}, [editingProduct?.info?.id, editablePerm, infoEditable, priceEditable]);
}, [editingProduct, editablePerm, infoEditable, priceEditable]);
const onFinish = (values) => {
console.log('Received values of form, origin form value: \n', values);
@ -78,8 +82,12 @@ const InfoForm = ({ onSubmit, onReset, onValuesChange, editablePerm, infoEditabl
const onIValuesChange = (changedValues, allValues) => {
const dest = formValuesMapper(allValues);
// console.log('form onValuesChange', Object.keys(changedValues), changedValues);
if ('product_title' in changedValues) {
const editTitle = (changedValues.product_title);
setPickEditedInfo({ ...pickEditedInfo, product_title: editTitle });
}
if (typeof onValuesChange === 'function') {
onValuesChange(dest);
onValuesChange(changedValues, dest);
}
};
const onFieldsChange = (hangedFields, allFields) => {
@ -130,7 +138,7 @@ const InfoForm = ({ onSubmit, onReset, onValuesChange, editablePerm, infoEditabl
},
}),
]}>
<ProductInfoLgc editable={infoEditable} formInstance={form} />
<ProductInfoLgc editable={infoEditable} formInstance={form} pickEditedInfo={pickEditedInfo} />
</Form.Item>
<Form.Item name='quotation'>
@ -243,19 +251,20 @@ function getFields(props) {
99,
<Form.Item name='recommends_rate' label={t('RecommendsRate')} {...fieldProps.recommends_rate} tooltip={t('FormTooltip.RecommendsRate')}>
{/* <Input placeholder={t('RecommendsRate')} allowClear /> */}
<Select
<InputNumber {...styleProps} {...editableProps('recommends_rate')} min={1} max={1000} />
{/* <Select
{...styleProps}
{...editableProps('recommends_rate')}
style={{ width: '100%' }}
labelInValue={false}
options={[
{ value: '1', label: 'Top 1' },
{ value: '2', label: 'Top 2' },
{ value: '3', label: 'Top 3' },
{ value: '4', label: '4' },
{ value: '5', label: '5' },
{ value: 1, label: 'Top 1' },
{ value: 2, label: 'Top 2' },
{ value: 3, label: 'Top 3' },
{ value: 4, label: '4' },
{ value: 5, label: '5' },
]}
/>
/> */}
</Form.Item>,
fieldProps?.recommends_rate?.col || midCol
),
@ -288,7 +297,7 @@ function getFields(props) {
labelInValue={false}
options={[
{ value: '153001', label: '在计划显示,不在报价信显示' },
{ value: 0, label: '计划和报价信都要显示' },
{ value: '0', label: '计划和报价信都要显示' },
{ value: '153001, 153002', label: '计划和报价信都不用显示' },
]}
{...styleProps}
@ -369,7 +378,7 @@ const formValuesMapper = (values) => {
],
'dept': { key: 'dept_id', transform: (value) => (typeof value === 'string' ? value : value?.value || value?.key || '') },
'open_weekdays': { key: 'open_weekdays', transform: (value) => (Array.isArray(value) ? value.join(',') : value) },
'recommends_rate': { key: 'recommends_rate', transform: (value) => (typeof value === 'string' ? value : value?.value || value?.key || '') },
// 'recommends_rate': { key: 'recommends_rate', transform: (value) => ((typeof value === 'string' || typeof value === 'number') ? value : value?.value || value?.key || '') },
// 'lgc_details': [
// {
// key: 'lgc_details',
@ -391,7 +400,7 @@ const formValuesMapper = (values) => {
key: 'lgc_details',
transform: (value) => {
const valueArr = Object.values(value)
.filter((_v) => !isEmpty(_v))
.filter((_v) => !isEmpty(_v.lgc))
.map((e) => ({ title: '', ...e, descriptions: e.descriptions || '' }));
return valueArr;
},
@ -400,7 +409,7 @@ const formValuesMapper = (values) => {
key: 'lgc_details_mapped',
transform: (value) => {
const valueArr = Object.values(value)
.filter((_v) => !isEmpty(_v))
.filter((_v) => !isEmpty(_v.lgc))
.map((e) => ({ title: '', ...e, descriptions: e.descriptions || '' }));
return valueArr.reduce((r, c) => ({ ...r, [c.lgc]: c }), {});
},

@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { useDefaultLgc } from '@/i18n/LanguageSwitcher';
import { cloneDeep, isEmpty, isNotEmpty } from '@/utils/commons';
const ProductInfoLgc = ({ editable, formInstance, ...props }) => {
const ProductInfoLgc = ({ editable, formInstance, pickEditedInfo, ...props }) => {
const { t } = useTranslation('products');
const { language: languageHT } = useDefaultLgc();
const HTLanguageSetsMapVal = useHTLanguageSetsMapVal();
@ -15,6 +15,13 @@ const ProductInfoLgc = ({ editable, formInstance, ...props }) => {
const [activeKey, setActiveKey] = useState();
const [items, setItems] = useState([]);
useEffect(() => {
formInstance.setFieldValue(['lgc_details_mapped', '2', 'title'], pickEditedInfo.product_title);
return () => {};
}, [pickEditedInfo.product_title]);
useEffect(() => {
const existsLgc = (editingProduct?.lgc_details || []).map((ele, li) => ({
...ele,
@ -39,7 +46,7 @@ const ProductInfoLgc = ({ editable, formInstance, ...props }) => {
<Form.Item name={['lgc_details_mapped', `${ele.lgc}`, 'descriptions']} label={t('products:Description')} initialValue={ele.descriptions} tooltip={t('FormTooltip.Description')}>
<Input.TextArea
className={'!text-slate-600'}
rows={3}
rows={3} maxLength={2000} showCount
allowClear
// onChange={(e) => handleChange('description', e.target.value)}
// disabled={ignoreEditable ? false : (!isEmpty(ele.descriptions) || !editable)}
@ -91,7 +98,7 @@ const ProductInfoLgc = ({ editable, formInstance, ...props }) => {
<Input allowClear placeholder={t(`FormTooltip.NewTitle.${editingProduct?.info?.product_type_id}`)} />
</Form.Item>
<Form.Item name={['lgc_details_mapped', `${lgcItem.value}`, 'descriptions']} preserve={false} label={t('products:Description')} tooltip={t('FormTooltip.Description')}>
<Input.TextArea rows={3} allowClear />
<Input.TextArea rows={3} maxLength={2000} showCount allowClear />
</Form.Item>
<Form.Item hidden name={['lgc_details_mapped', `${lgcItem.value}`, 'lgc']} preserve={false} initialValue={lgcItem.value}>
<Input />

@ -139,7 +139,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
const [isQuotationModalOpen, setQuotationModalOpen] = useState(false)
const [isBatchSetupModalOpen, setBatchSetupModalOpen] = useState(false)
const { modal, notification } = App.useApp();
const { modal, notification } = App.useApp()
const [quotationForm] = Form.useForm()
const [batchSetupForm] = Form.useForm()
@ -215,7 +215,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
{
title: t('products:group_size'),
dataIndex: 'group_size',
width: '4rem',
width: '6rem',
render: (_, record) => `${record.group_size_min}-${record.group_size_max}`,
},
@ -247,7 +247,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
return (
<>
<h2>{t('products:EditComponents.Quotation')}</h2>
<Table
<Table size='small'
bordered
dataSource={quotationList}
columns={quotationColumns}

@ -1,10 +1,13 @@
import { createContext, useEffect, useState } from 'react';
import { Tree, Input } from 'antd';
import { useLocation } from 'react-router-dom';
import { Tree, Input, Divider } from 'antd';
import { CaretDownOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import useProductsStore from '@/stores/Products/Index';
import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import { groupBy, sortBy } from '@/utils/commons';
import NewProductModal from './NewProductModal';
import ContractRemarksModal from './ContractRemarksModal'
const flattenTreeFun = (tree) => {
let flatList = [];
@ -40,7 +43,10 @@ const getParentKey = (key, tree) => {
const ProductsTree = ({ onNodeSelect, ...props }) => {
const { t } = useTranslation();
const location = useLocation();
const isEditPage = location.pathname.includes('edit');
const [agencyProducts, editingProduct, setEditingProduct] = useProductsStore((state) => [state.agencyProducts, state.editingProduct, state.setEditingProduct]);
const [activeAgency] = useProductsStore((state) => [state.activeAgency]);
const productsTypes = useProductsTypes();
const [treeData, setTreeData] = useState([]);
@ -49,6 +55,11 @@ const ProductsTree = ({ onNodeSelect, ...props }) => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
useEffect(() => {
setExpandedKeys(productsTypes.map((item) => item.key)); //
return () => {}
}, [productsTypes, activeAgency]);
useEffect(() => {
// ;
// const title = text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || '';
@ -70,7 +81,7 @@ const ProductsTree = ({ onNodeSelect, ...props }) => {
const lgc_map = product.lgc_details.reduce((rlgc, clgc) => ({...rlgc, [clgc.lgc]: clgc}), {});
return {
// title: product.info.title || lgc_map?.['2']?.title || lgc_map?.['1']?.title || '',
title: `${product.info.city_name}` + (product.info.title || lgc_map?.['2']?.title || lgc_map?.['1']?.title || ''),
title: `${product.info.city_name}` + (product.info.title || lgc_map?.['2']?.title || lgc_map?.['1']?.title || product.info.product_title || ''),
// key: `${ele.value}-${product.info.id}`,
key: product.info.id,
_raw: product,
@ -89,7 +100,7 @@ const ProductsTree = ({ onNodeSelect, ...props }) => {
setRawTreeData(_show);
setFlattenTreeData(flattenTreeFun(_show));
setExpandedKeys(productsTypes.map((item) => item.key)); //
// setExpandedKeys(productsTypes.map((item) => item.key)); //
// setActiveKey(isEmpty(_show) ? [] : [_show[0].key]);
return () => {};
@ -144,6 +155,12 @@ const ProductsTree = ({ onNodeSelect, ...props }) => {
return (
<div className={`${props.className} relative`} style={props.style}>
<Input.Search placeholder='Search' onChange={onSearch} allowClear className='sticky top-1 z-20 mb-3' />
{/* 编辑 */}
{isEditPage && (
<NewProductModal />
)}
<Divider type='vertical' />
<ContractRemarksModal />
<Tree
blockNode
showLine defaultExpandAll expandAction={'doubleClick'}

@ -74,7 +74,7 @@ function Index() {
return <span className={stateCls}>{stateMapVal[`${r.audit_state_id}`]?.label}</span>;
},
},
{ title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by_name' },
{ title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by' },
{ title: t('products:AuditDate'), key: 'audit_date', dataIndex: 'audit_date' },
{
title: '',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -43,7 +43,7 @@ function Detail() {
];
function detailTextRender(_, confirm) {
const formattedText = confirm.PCI_ConfirmText;//.replace(/\;/g, '\n\n');
const formattedText = confirm.PCI_ConfirmText;
return (
<div className='whitespace-pre-line'>
{formattedText}
@ -76,6 +76,8 @@ function Detail() {
const [confirmText, setConfirmText] = useState('');
const [newConfirmText, setNewConfirmText] = useState('');
const [dataLoading, setDataLoading] = useState(false);
const [reservationPreviewUrl, setReservationPreviewUrl] = useState('');
const [nameCardPreviewUrl, setNameCardPreviewUrl] = useState('');
const { notification } = App.useApp();
const { reservationId } = useParams();
@ -84,17 +86,14 @@ function Detail() {
const [getReservationDetail, reservationDetail, confirmationList, selectConfirmation, submitConfirmation] =
useReservationStore((state) =>
[state.getReservationDetail, state.reservationDetail, state.confirmationList, state.selectConfirmation, state.submitConfirmation])
const randomString = new Date().getTime()
const officeWebViewerUrl =
'https://view.officeapps.live.com/op/embed.aspx?wdPrint=1&wdHideGridlines=0&wdHideComments=1&wdEmbedCode=0&src=';
// https://www.chinahighlights.com/public/reservationW220420009.doc
const reservationUrl =
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${travelAgencyId}&token=${loginToken}&FileType=1`;
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${travelAgencyId}&token=${loginToken}&FileType=1&v=${randomString}`
const nameCardUrl =
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${travelAgencyId}&token=${loginToken}&FileType=2`;
const reservationPreviewUrl = officeWebViewerUrl + encodeURIComponent(reservationUrl);
const nameCardPreviewUrl = officeWebViewerUrl + encodeURIComponent(nameCardUrl);
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${travelAgencyId}&token=${loginToken}&FileType=2&v=${randomString}`
const showConfirmModal = (confirm) => {
setIsModalOpen(true);
@ -118,6 +117,10 @@ function Detail() {
useEffect(() => {
setDataLoading(true);
setReservationPreviewUrl(officeWebViewerUrl + encodeURIComponent(reservationUrl))
setNameCardPreviewUrl(officeWebViewerUrl + encodeURIComponent(nameCardUrl))
getReservationDetail(reservationId)
.catch(ex => {
notification.error({

@ -0,0 +1,140 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Spin, Input, Col, Row, Space, List, Table, Button, Typography } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined, AuditOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
import dayjs from "dayjs";
import SearchForm from "@/components/SearchForm";
import { DATE_FORMAT } from "@/config";
import { TableExportBtn } from "@/components/Data";
import trainTicketStore from "@/stores/Trainticket";
import { usingStorage } from "@/hooks/usingStorage";
const planListColumns = [
{
title: "团名",
key: "GRI_No",
dataIndex: "GRI_No",
// sorter: (a, b) => b.GRI_No - a.GRI_No,
render: (text, record) => (
<Typography.Text title={record.Memo}>
{record.GRI_No} {record.WL}
</Typography.Text>
),
},
{
title: "人数",
dataIndex: "PersonNum",
key: "PersonNum",
},
{
title: "出发",
key: "FromAirport",
dataIndex: "FromAirport",
},
{
title: "抵达",
key: "ToAirport",
dataIndex: "ToAirport",
},
{
title: "车次",
key: "FlightNo",
dataIndex: "FlightNo",
},
{
title: "出发日期",
key: "StartDate",
dataIndex: "StartDate",
sorter: (a, b) => {
const dateA = new Date(a.StartDate);
const dateB = new Date(b.StartDate);
return dateB.getTime() - dateA.getTime();
},
},
{
title: "开始时间",
key: "FlightStart",
dataIndex: "FlightStart",
render: text => formatColonTime(text),
},
{
title: "抵达时间",
key: "FlightEnd",
dataIndex: "FlightEnd",
render: text => formatColonTime(text),
},
{
title: "出票处理",
key: "TicketIssued",
dataIndex: "TicketIssued",
render: (text, record) => record.TicketIssuedName,
},
{
title: "计划状态",
key: "FlightStatus",
dataIndex: "FlightStatus",
render: (text, record) => record.FlightStatusName,
},
{
title: "操作",
key: "FlightInfo",
dataIndex: "FlightInfo",
render: (text, record) => <NavLink to={`/trainticket/plan/${record.COLI_SN}/${record.GRI_SN}`}>{"编辑"}</NavLink>,
},
];
const Trainticket = props => {
const navigate = useNavigate();
const { travelAgencyId } = usingStorage();
const [getPlanList, planList, loading] = trainTicketStore(state => [state.getPlanList, state.planList, state.loading]);
const showTotal = total => `合计 ${total} `;
useEffect(() => {
!planList && getPlanList(travelAgencyId, "", dayjs().startOf("M").format(DATE_FORMAT), dayjs().endOf("M").format(DATE_FORMAT), "-1", "-1");
}, []);
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row>
<Col md={20} lg={20} xxl={20}>
<SearchForm
initialValue={{
dates: [dayjs().startOf("M"), dayjs().endOf("M")],
}}
fieldsConfig={{
shows: ["referenceNo", "dates", "airticket_state", "plan_state"],
fieldProps: {
referenceNo: { label: "搜索计划" },
dates: { label: "出发日期", col: 8 },
},
}}
onSubmit={(err, formVal, filedsVal) => {
getPlanList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.endtime, formVal.plan_state, formVal.airticket_state);
}}
/>
</Col>
<Col md={4} lg={4} xxl={4}>
<Space>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/trainticket/invoice`)}>
报账
</Button>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/trainticket/invoicepaid`)}>
汇款记录
</Button>
</Space>
</Col>
</Row>
<Row gutter={16}>
<Col md={24} lg={24} xxl={24}>
<Table bordered={true} rowKey="id" columns={planListColumns} dataSource={planList} loading={loading} pagination={{ defaultPageSize: 20, showTotal: showTotal }} />
<TableExportBtn btnTxt="导出计划" label={`出票计划`} {...{ columns: planListColumns, dataSource: planList }} />
</Col>
<Col md={24} lg={24} xxl={24}></Col>
</Row>
</Space>
);
};
export default Trainticket;

@ -0,0 +1,245 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Steps, Statistic, Col, Row, Space, Checkbox, Table, Button, App, Typography } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, FrownTwoTone, LikeTwoTone } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
import dayjs from "dayjs";
import SearchForm from "@/components/SearchForm";
import BackBtn from "@/components/BackBtn";
import { TableExportBtn } from "@/components/Data";
import trainTicketStore from "@/stores/Trainticket";
import { usingStorage } from "@/hooks/usingStorage";
const Invoice = props => {
const navigate = useNavigate();
const { notification } = App.useApp();
const { travelAgencyId } = usingStorage();
const [getVEIFlightBill, vEIFlightBill, loading, postVEIFlightBillSubmit] = trainTicketStore(state => [state.getVEIFlightBill, state.vEIFlightBill, state.loading, state.postVEIFlightBillSubmit]);
const showTotal = total => `合计 ${total} `;
const [selectedValues, setSelectedValues] = useState([]);
const vEIFlightBillColumns = [
{
title: "团名",
key: "GRI_No",
dataIndex: "GRI_No",
render: (text, record) => (
<Typography.Text title={record.Memo}>
{record.GRI_No} {record.WL}
</Typography.Text>
),
},
{
title: "状态",
key: "CostType",
dataIndex: "CostType",
},
{
title: "出发日期",
key: "StartDate",
dataIndex: "StartDate",
sorter: (a, b) => {
const dateA = new Date(a.StartDate);
const dateB = new Date(b.StartDate);
return dateB.getTime() - dateA.getTime();
},
},
{
title: "出发",
key: "CLF_FromAirport",
dataIndex: "CLF_FromAirport",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "抵达",
key: "ToAirport",
dataIndex: "ToAirport",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "车次",
key: "FlightNo",
dataIndex: "FlightNo",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "取票号",
key: "TicketNo",
dataIndex: "TicketNo",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "车票类型",
key: "SeatClass",
dataIndex: "SeatClass",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "车票价格",
children: [
{
title: vEIFlightBill && vEIFlightBill.reduce((acc, curr) => acc + curr.Cost, 0),
dataIndex: "Cost",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
],
key: "Cost",
},
{
title: "服务费",
children: [
{
title: vEIFlightBill && vEIFlightBill.reduce((acc, curr) => acc + curr.ServiceFee, 0),
dataIndex: "ServiceFee",
},
],
},
{
title: "审核状态",
children: [
{
title: (
<Button type="link" onClick={() => checkALL()}>
全选
</Button>
),
dataIndex: "CheckStatus", //2
render: (text, record) =>
record.CheckStatus < 2 ? (
<Checkbox onChange={event => handleCheckboxChange(event, record)} checked={checkboxStates(record.CLC_SN)}>
待提交
</Checkbox>
) : (
<Steps
size="small"
current={record.CheckStatus - 1}
items={[
{
title: "提交账单",
},
{
title: "顾问审核",
},
{
title: "财务处理",
},
{
title: "账单支付",
},
]}
/>
),
},
],
sorter: (a, b) => {
return b.CheckStatus - a.CheckStatus;
},
},
];
//
const checkboxStates = CLC_SN => {
return selectedValues.some(v => v.CLC_SN === CLC_SN);
};
// checkbox
const handleCheckboxChange = (event, data) => {
const value = { CLC_SN: data.CLC_SN, WL: data.WL, OPI_SN: data.OPI_SN, OPI_Email: data.OPI_Email, GRI_SN: data.GRI_SN, GRI_Name: data.GRI_Name, Cost: data.Cost, ServiceFee: data.ServiceFee };
if (event.target.checked) {
setSelectedValues([...selectedValues, value]); //
} else {
setSelectedValues(selectedValues.filter(v => v.CLC_SN !== value.CLC_SN)); //
}
};
//
const postInvoice = () => {
postVEIFlightBillSubmit(travelAgencyId, selectedValues)
.then(() => {
notification.success({
message: `成功`,
description: "账单提交成功!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
setSelectedValues([]); //
})
.catch(() => {
notification.error({
message: `错误`,
description: "保存失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
};
//
const checkALL = () => {
if (isEmpty(vEIFlightBill)) return;
const allChecked = selectedValues.length === vEIFlightBill.filter(item => item.CheckStatus < 2).length;
setSelectedValues(
allChecked
? []
: vEIFlightBill
.filter(item => item.CheckStatus < 2)
.map(item => ({ CLC_SN: item.CLC_SN, WL: item.WL, OPI_SN: item.OPI_SN, OPI_Email: item.OPI_Email, GRI_SN: item.GRI_SN, GRI_Name: item.GRI_Name, Cost: item.Cost, ServiceFee: item.ServiceFee }))
); //
};
useEffect(() => {}, []);
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row>
<Col md={20} lg={20} xxl={20}>
<SearchForm
initialValue={{
dates: [dayjs().startOf("M"), dayjs().endOf("M")],
}}
fieldsConfig={{
shows: ["referenceNo", "dates", "invoiceCheckStatus"],
fieldProps: {
referenceNo: { label: "搜索计划" },
dates: { label: "出发日期", col: 8 },
},
}}
onSubmit={(err, formVal, filedsVal) => {
getVEIFlightBill(travelAgencyId, formVal.referenceNo, formVal.invoiceCheckStatus, formVal.startdate, formVal.endtime);
console.log(vEIFlightBill);
}}
/>
</Col>
<Col md={4} lg={4} xxl={4}>
<BackBtn to={"/trainticket"} />
</Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24}>
<Table bordered={true} rowKey="CLC_SN" columns={vEIFlightBillColumns} dataSource={vEIFlightBill} loading={loading} pagination={{ defaultPageSize: 100, showTotal: showTotal }} />
<TableExportBtn btnTxt="导出账单" label={`车票账单`} {...{ columns: vEIFlightBillColumns, dataSource: vEIFlightBill }} />
</Col>
<Col md={24} lg={24} xxl={24}></Col>
</Row>
<Row>
<Col md={24} lg={18} xxl={18}></Col>
<Col md={24} lg={2} xxl={2}>
<Statistic title="已选车票价格" value={selectedValues.reduce((acc, curr) => acc + curr.Cost, 0)} />
</Col>
<Col md={24} lg={2} xxl={2}>
<Statistic title="已选服务费" value={selectedValues.reduce((acc, curr) => acc + curr.ServiceFee, 0)} />
</Col>
<Col md={24} lg={2} xxl={2}>
<Button type="primary" size="large" onClick={postInvoice} disabled={selectedValues.length == 0}>
提交账单
</Button>
</Col>
</Row>
</Space>
);
};
export default Invoice;

@ -0,0 +1,136 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Spin, Input, Col, Row, Space, Checkbox, Table, Button, App } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, FrownTwoTone, LikeTwoTone } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime, formatDate, isNotEmpty } from "@/utils/commons";
import { DATE_FORMAT } from "@/config";
import dayjs from "dayjs";
import SearchForm from "@/components/SearchForm";
import BackBtn from "@/components/BackBtn";
import { TableExportBtn } from "@/components/Data";
import useInvoiceStore from "@/stores/Invoice";
import { fetchInvoicePaidDetail } from "@/stores/Invoice";
import { usingStorage } from "@/hooks/usingStorage";
const InvoicePaid = props => {
const navigate = useNavigate();
const { notification } = App.useApp();
const { travelAgencyId } = usingStorage();
const [invoicePaidDetail, setInvoicePaidDetail] = useState([]);
const [invoiceNO, setInvoiceNO] = useState([]); //
const [loading, invoicePaid, fetchInvoicePaid] = useInvoiceStore(state => [state.loading, state.invoicePaid, state.fetchInvoicePaid]);
const showTotal = total => `Total ${total} items`;
const showTotal_detail = total => `Total ${total} items`;
useEffect(() => {
// fetchInvoicePaid(travelAgencyId, "", dayjs().subtract(2, "M").startOf("M").format(DATE_FORMAT), dayjs().endOf("M").format(DATE_FORMAT));
}, []);
const invoicePaidColumns = [
{
title: "编号",
dataIndex: "fl_finaceNo",
key: "fl_finaceNo",
},
{
title: "报账日期",
key: "fl_adddate",
dataIndex: "fl_adddate",
render: (text, record) => (isNotEmpty(text) ? formatDate(new Date(text)) : ""),
},
{
title: "团数",
key: "fcount",
dataIndex: "fcount",
},
{
title: "总额",
key: "pSum",
dataIndex: "pSum",
//render: (text, record) => (isNotEmpty(record.GMD_Currency) ? record.GMD_Currency + " " + text : text),
},
{
title: "查看",
key: "pSum",
dataIndex: "pSum",
render: (text, record) => (
<Button
type="link"
onClick={() => {
fetchInvoicePaidDetail(travelAgencyId, record.key).then(res => setInvoicePaidDetail(res));
setInvoiceNO(record.fl_finaceNo);
}}>
查看明细
</Button>
),
},
];
const invoicePaidDetailColumns = [
{
title: "团号",
dataIndex: "fl2_GroupName",
key: "fl2_GroupName",
},
{
title: "金额",
key: "fl2_price",
dataIndex: "fl2_price",
},
{
title: "报账日期",
key: "fl2_ArriveDate",
dataIndex: "fl2_ArriveDate",
render: (text, record) => (isNotEmpty(text) ? formatDate(new Date(text)) : ""),
},
{
title: "顾问",
dataIndex: "fl2_wl",
key: "fl2_wl",
},
];
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row>
<Col md={20} lg={20} xxl={20}>
<SearchForm
initialValue={{
dates: [dayjs().subtract(2, "M").startOf("M"), dayjs().endOf("M")],
}}
fieldsConfig={{
shows: ["dates"],
fieldProps: {
dates: { col: 10, label: "报账日期" },
},
}}
onSubmit={(err, formVal) => {
fetchInvoicePaid(travelAgencyId, "", formVal.startdate, formVal.enddate);
setInvoicePaidDetail([]);
}}
/>
</Col>
<Col md={4} lg={4} xxl={4}>
<BackBtn to={"/trainticket"} />
</Col>
</Row>
<Row>
<Col md={24} lg={16} xxl={16}>
<Divider orientation="left">汇款列表</Divider>
<Table bordered columns={invoicePaidColumns} dataSource={invoicePaid} loading={loading} pagination={{ defaultPageSize: 20, showTotal: showTotal }} />
</Col>
<Col md={24} lg={4} xxl={4}></Col>
</Row>
<Row>
<Col md={24} lg={20} xxl={20}>
<Divider orientation="left">账单明细 {invoiceNO}</Divider>
<Table bordered columns={invoicePaidDetailColumns} dataSource={invoicePaidDetail} pagination={{ defaultPageSize: 100, showTotal: showTotal_detail }} />
<TableExportBtn btnTxt="导出账单明细" label={`车票账单`} {...{ columns: invoicePaidDetailColumns, dataSource: invoicePaidDetail }} />
</Col>
<Col md={24} lg={4} xxl={4}></Col>
</Row>
</Space>
);
};
export default InvoicePaid;

@ -0,0 +1,706 @@
import { useState, useEffect } from "react";
import { Checkbox, Divider, DatePicker, Modal, Form, Input, Col, Row, Space, Collapse, Table, Button, Select, App, Popconfirm, Switch, Radio, List } from "antd";
import { PhoneOutlined, FrownTwoTone, LikeTwoTone, ArrowUpOutlined, ArrowDownOutlined, PlusOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
import { OFFICEWEBVIEWERURL } from "@/config";
import dayjs from "dayjs";
import trainTicketStore from "@/stores/Trainticket";
import { usingStorage } from "@/hooks/usingStorage";
import BackBtn from "@/components/BackBtn";
import { station_names } from "@/views/trainticket/station_name";
const TrainticketPlan = props => {
const { coli_sn, gri_sn } = useParams();
const { travelAgencyId, loginToken, userId } = usingStorage();
const [
getPlanDetail,
planDetail,
getGuestList,
guestList,
loading,
postFlightDetail,
postFlightCost,
deleteFlightCost,
getVeiPlanChange,
veiPlanChangeTxt,
postVeiFlightPlanConfirm,
ticketIssuedNotifications,
delete_flight_info,
seatTable,
] = trainTicketStore(state => [
state.getPlanDetail,
state.planDetail,
state.getGuestList,
state.guestList,
state.loading,
state.postFlightDetail,
state.postFlightCost,
state.deleteFlightCost,
state.getVeiPlanChange,
state.veiPlanChangeTxt,
state.postVeiFlightPlanConfirm,
state.ticketIssuedNotifications,
state.delete_flight_info,
state.seatTable,
]);
const reservationUrl = `https://p9axztuwd7x8a7.mycht.cn/Service_BaseInfoWeb/FlightPlanDocx?GRI_SN=${gri_sn}&VEI_SN=${travelAgencyId}&token=${loginToken}`;
const reservationPreviewUrl = OFFICEWEBVIEWERURL + encodeURIComponent(reservationUrl);
const [form] = Form.useForm();
const { notification } = App.useApp();
//
const guestList_select = () => {
return (
guestList &&
guestList.map(item => {
return { label: `${item.MEI_Name} , ${item.MEI_PassportNo}`, value: `${item.MEI_Name} , ${item.MEI_PassportNo} , ${item.MEI_Country} , ${item.MEI_Gender} , ${item.MEI_age} , ${item.MEI_Birthday}` };
})
);
};
const station_name_select = rawStr => {
const records = rawStr.split("@").filter(item => item); // 1. @
return records.map(record => {
// |8
const [
abbreviation, // bjb
name, //
code, // VAP
pinyin, // beijingbei
shortPinyin, // bjb
sequence, // 0
areaCode, // 0357
city, //
] = record.split("|");
return {
value: name + "-" + pinyin,
label: name,
};
});
};
const guestList_OnChange = e => {
ticket_form.setFieldsValue({ Memo: `${e.target.value}` });
};
//
const seatTableList_select = () => {
return (
seatTable &&
seatTable.map(item => {
return { label: `${item.name}`, value: `${item.name}` };
})
);
};
//
const costListColumns = [
{
title: "客人信息/备注",
key: "Memo",
dataIndex: "Memo",
},
{
title: "费用类型",
key: "CostType",
dataIndex: "CostType",
},
{
title: "车厢",
key: "Cabin",
dataIndex: "Cabin",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "座位号",
key: "SeatNo",
dataIndex: "SeatNo",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "座位类型",
key: "SeatClass",
dataIndex: "SeatClass",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "车票价格",
key: "Cost",
dataIndex: "Cost",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "服务费",
key: "ServiceFee",
dataIndex: "ServiceFee",
},
{
title: "编辑",
key: "CLC_SN",
dataIndex: "CLC_SN",
render: (text, record) =>
record.CheckStatus <= 2 ? (
<Space>
<a onClick={() => showModal(record)}>编辑</a>
<Popconfirm title="删除" description="请确认是否删除?" onConfirm={() => handleDelete(record.CLC_SN)} okText="是" cancelText="否">
<Button danger type="link">
删除
</Button>
</Popconfirm>
</Space>
) : (
record.CheckStatusName
),
},
];
const Trainticket_form = props => {
const trainInfo = props.airInfo;
const [traininfo_form] = Form.useForm();
return (
<>
<Form
form={traininfo_form}
name={"ticket_form_" + trainInfo.id}
labelCol={{
span: 6,
}}
wrapperCol={{
span: 16,
}}
initialValues={{ ...trainInfo, StartDate: dayjs(trainInfo.StartDate) }}
onFinish={values => {
postFlightDetail(trainInfo.CLF_SN, trainInfo.GRI_SN, trainInfo.VEI_SN, trainInfo, values)
.then(() => {
notification.success({
message: `成功`,
description: "车票信息保存成功!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
})
.catch(() => {
notification.error({
message: `错误`,
description: "保存失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
}}
autoComplete="off">
<Divider orientation="left">火车信息</Divider>
<Row gutter={16}>
<Col md={24} lg={20} xxl={20}>
<Form.Item label="出发日期、车次、取票号" required>
<Space>
<Form.Item name="StartDate" noStyle rules={[{ required: true, message: "请输入出发日期!" }]}>
<DatePicker
placeholder="出发日期"
style={{
minWidth: 160,
}}
/>
</Form.Item>
<Form.Item name="FlightNo" noStyle rules={[{ required: true, message: "请输入车次!" }]}>
<Input placeholder="车次" />
</Form.Item>
<Form.Item name="TicketNo" noStyle rules={[{ required: true, message: "请输入取票号!" }]}>
<Input placeholder="取票号" maxLength={9} />
</Form.Item>
</Space>
</Form.Item>
<Form.Item label="出发站、抵达站" required>
<Space>
<Form.Item name="FromAirport" noStyle rules={[{ required: true, message: "请输入出发站!" }]}>
{/* <Input placeholder="出发" /> */}
<Select
placeholder="出发"
showSearch
style={{
minWidth: 160,
}}
options={station_name_select(station_names)}
/>
</Form.Item>
<Form.Item name="FlightStart" noStyle rules={[{ required: true, message: "请输入出发时间!" }]}>
<Input placeholder="出发时间" />
</Form.Item>
-
<Form.Item name="ToAirport" noStyle rules={[{ required: true, message: "请输入抵达站!" }]}>
{/* <Input placeholder="抵达" /> */}
<Select
placeholder="抵达"
showSearch
style={{
minWidth: 160,
}}
options={station_name_select(station_names)}
/>
</Form.Item>
<Form.Item name="FlightEnd" noStyle rules={[{ required: true, message: "请输入抵达时间!" }]}>
<Input placeholder="抵达时间" />
</Form.Item>
</Space>
</Form.Item>
<Form.Item name="ServiceType" hidden initialValue="2"></Form.Item>
</Col>
<Col md={24} lg={4} xxl={4}>
<Space direction="vertical">
<Form.Item name="TicketIssued">
<Switch checkedChildren="已处理" unCheckedChildren="未处理" />
</Form.Item>
<Button type="primary" htmlType="submit">
1. 保存车票信息
</Button>
</Space>
</Col>
</Row>
<Divider orientation="left">出票信息</Divider>
<Row gutter={16}>
<Col md={24} lg={20} xxl={20}>
<Table bordered={true} rowKey="CLC_SN" columns={costListColumns} dataSource={trainInfo.Flightcost_AsJOSN} loading={loading} pagination={false} />
</Col>
<Col md={24} lg={4} xxl={4}>
<Space direction="vertical">
<Button type="primary" onClick={() => showModal(trainInfo)}>
2. 添加出票信息
</Button>
</Space>
</Col>
</Row>
<Divider orientation="left"></Divider>
<Row gutter={16}>
<Col md={24} lg={20} xxl={20}>
<Form.Item label="上下站提醒信息" name="FlightMemo_messages">
<Input placeholder="没有提醒请留空,信息会抄送给上下站地接" />
</Form.Item>
<Form.Item label="已发提醒" name="FlightMemo">
<Input.TextArea rows={4} readOnly disabled />
</Form.Item>
</Col>
<Col md={24} lg={4} xxl={4}>
<Button
type="primary"
onClick={() => {
ticketIssuedNotifications(userId, trainInfo.CLF_SN, trainInfo.OPI_SN, traininfo_form.getFieldValue("FlightMemo_messages"))
.then(() => {
notification.success({
message: `成功`,
description: "提醒信息已发出!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
traininfo_form.setFieldValue("FlightMemo_messages", "");
})
.catch(() => {
notification.error({
message: `错误`,
description: "提醒失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
}}>
3. 通知顾问
</Button>
</Col>
</Row>
</Form>
</>
);
};
const detail_items = () => {
return planDetail
? planDetail.map(item => {
return {
key: item.id,
label: `${item.StartDate} ${item.FlightNo}(${item.FromAirport}${item.FlightStart}-${item.ToAirport}${item.FlightEnd})(${item.FlightCabin})`,
extra: (
<Popconfirm
title="请确认要删除车票记录"
description=""
onConfirm={() => {
delete_flight_info(item.CLF_SN); //
getPlanDetail(travelAgencyId, gri_sn); //
}}
okText="是"
cancelText="否">
<Button type="dashed" size="small" disabled={item.Flightcost_AsJOSN.length == 0 ? false : true}>
删除
</Button>
</Popconfirm>
),
children: <Trainticket_form airInfo={item} />,
};
})
: [];
};
// begin
const [isModalOpen, setIsModalOpen] = useState(false);
const [isModalOpen_confirmInfo, setisModalOpen_confirmInfo] = useState(false);
const [isTicketType, setisTicketType] = useState(true);
const [isAddNew, setisAddNew] = useState(true); //
const [ticket_form] = Form.useForm();
const [confirmInfo_form] = Form.useForm();
const showModal = ticket => {
setIsModalOpen(true);
ticket_form.resetFields();
if (isEmpty(ticket.CostType)) ticket.CostType = "出票";
ticket.CostType == "出票" ? setisTicketType(true) : setisTicketType(false); //
const isNew = isEmpty(ticket.CLC_SN); //
setisAddNew(isNew);
if (isNew) {
ticket.ServiceFee = "60"; // // 60
}
ticket_form.setFieldsValue(ticket);
if (isEmpty(ticket.Memo)) ticket_form.setFieldsValue({ Memo: "" });
};
const handleOk = (close_modal = true) => {
ticket_form
.validateFields()
.then(values => {
//
postFlightCost(values)
.then(() => {
notification.success({
message: `成功`,
description: "保存成功!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
getPlanDetail(travelAgencyId, gri_sn);
})
.catch(() => {
notification.error({
message: `错误`,
description: "保存失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
if (close_modal) setIsModalOpen(false);
})
.catch(info => {
console.log("Validate Failed:", info);
});
};
const handleCancel = () => {
ticket_form.resetFields();
setIsModalOpen(false);
};
const handleDelete = CLC_SN => {
deleteFlightCost(CLC_SN)
.then(() => {
notification.success({
message: `成功`,
description: "删除成功!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
getPlanDetail(travelAgencyId, gri_sn);
})
.catch(() => {
notification.error({
message: `错误`,
description: "删除失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
};
const onChangeType = value => {
if (value == "出票") {
setisTicketType(true);
} else {
setisTicketType(false);
}
};
//
const showModal_confirmInfo = ConfirmInfo => {
setisModalOpen_confirmInfo(true);
confirmInfo_form.setFieldsValue({ ConfirmInfo: ConfirmInfo });
};
const handleCancel_confirmInfo = () => {
setisModalOpen_confirmInfo(false);
confirmInfo_form.resetFields();
};
const handleOk_confirmInfo = () => {
confirmInfo_form
.validateFields()
.then(values => {
console.log("Received values of form: ", values.ConfirmInfo);
postVeiFlightPlanConfirm(travelAgencyId, gri_sn, userId, values.ConfirmInfo)
.then(() => {
notification.success({
message: `成功`,
description: "保存成功!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
getVeiPlanChange(travelAgencyId, gri_sn);
})
.catch(() => {
notification.error({
message: `错误`,
description: "保存失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
confirmInfo_form.resetFields();
setisModalOpen_confirmInfo(false);
})
.catch(info => {
console.log("Validate Failed:", info);
});
};
// end
useEffect(() => {
getPlanDetail(travelAgencyId, gri_sn); //
getGuestList(coli_sn); //
getVeiPlanChange(travelAgencyId, gri_sn); //
}, []);
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row>
<Col md={20} lg={20} xxl={20}></Col>
<Col md={4} lg={4} xxl={4}>
<BackBtn to={"/trainticket"} />
</Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24} style={{ height: "100%" }}>
<iframe id="msdoc-iframe-reservation" title="msdoc-iframe-reservation" src={reservationPreviewUrl + "&v=" + Math.random()} style={{ width: "100%", height: "600px" }}></iframe>
<Button type="link" target="_blank" href={reservationUrl}>
下载
</Button>
</Col>
</Row>
<Row>
<Divider orientation="center">{planDetail ? `${planDetail[0].GRI_No} - ${planDetail[0].WL}` : ""}</Divider>
<Col md={24} lg={24} xxl={24}>
<Collapse items={detail_items()} />
</Col>
<Col md={24} lg={24} xxl={24}>
<br />
<p style={{ textAlign: "right" }}>
<Popconfirm
title="请确认要增加车票记录"
description=""
onConfirm={() => {
postFlightDetail("", gri_sn, travelAgencyId, { FlightNo: "新的记录", FlightStatus: 1, ServiceType: 2 }, []); //
getPlanDetail(travelAgencyId, gri_sn); //
}}
okText="是"
cancelText="否">
<Button type="dashed" icon={<PlusOutlined />}>
新增车票记录
</Button>
</Popconfirm>
</p>
</Col>
</Row>
<Row>
<Divider orientation="left">计划变更</Divider>
<Col md={24} lg={12} xxl={12}>
<Space direction="vertical" style={{ width: "90%" }}>
<Input.TextArea rows={16} readOnly value={veiPlanChangeTxt && veiPlanChangeTxt.ChangeText} />
<Button
type="primary"
onClick={() => {
showModal_confirmInfo(veiPlanChangeTxt && veiPlanChangeTxt.ChangeText);
}}
disabled={isEmpty(veiPlanChangeTxt) || isEmpty(veiPlanChangeTxt.ChangeText)}>
确认变更
</Button>
</Space>
</Col>
<Col md={24} lg={12} xxl={12}>
<Input.TextArea rows={16} readOnly value={veiPlanChangeTxt && veiPlanChangeTxt.ConfirmInfo} />
</Col>
</Row>
<Modal title="变更" open={isModalOpen_confirmInfo} onOk={handleOk_confirmInfo} onCancel={handleCancel_confirmInfo}>
<Form
form={confirmInfo_form}
labelCol={{
span: 5,
}}>
<Form.Item label="确认信息" name="ConfirmInfo" rules={[{ required: true }]}>
<Input.TextArea rows={4} />
</Form.Item>
</Form>
</Modal>
<Modal
title="费用信息"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
okText="保存"
cancelText="关闭"
footer={(_, { OkBtn, CancelBtn }) => (
<>
<CancelBtn />
{isAddNew ? (
<>
<Button type="primary" onClick={() => handleOk(false)}>
添加并继续新增
</Button>{" "}
<Button type="primary" onClick={() => handleOk(true)}>
添加并关闭
</Button>
</>
) : (
<OkBtn />
)}
</>
)}>
<Form
form={ticket_form}
labelCol={{
span: 5,
}}>
<Form.Item label="费用类型" name="CostType">
<Select
style={{
width: 160,
}}
onChange={onChangeType}
options={[
{
value: "出票",
label: "出票",
},
{
value: "改签",
label: "改签",
},
{
value: "取消",
label: "取消",
},
]}
/>
</Form.Item>
{isTicketType && (
<>
<Form.Item label="车厢" name="Cabin" rules={[{ required: true }]}>
<Select
style={{
width: 160,
}}
options={Array.from({ length: 16 }, (_, index) => ({
value: `${String(index + 1).padStart(2, "0")}`,
label: `${String(index + 1).padStart(2, "0")}`,
}))}
/>
</Form.Item>
<Form.Item label="座位号" name="SeatNo" rules={[{ required: true }]}>
<Input
style={{
width: 160,
}}
/>
</Form.Item>
<Form.Item label="座位类型" name="SeatClass" rules={[{ required: true }]}>
<Select
style={{
width: 160,
}}
options={seatTableList_select()}
/>
</Form.Item>
<Form.Item label="车票价格" name="Cost" rules={[{ required: true }]}>
<Input
prefix="¥"
style={{
width: 160,
}}
/>
</Form.Item>
</>
)}
<Form.Item label="服务费" name="ServiceFee" rules={[{ required: true }]}>
<Input
prefix="¥"
style={{
width: 160,
}}
/>
</Form.Item>
{isTicketType && (
<>
<Form.Item label="选择客人" name="MEI_Name66">
<Radio.Group
onChange={e => guestList_OnChange(e)}
style={{
minWidth: 320,
}}>
<List
bordered
dataSource={guestList_select()}
renderItem={item => (
<List.Item>
<Radio value={item.value}>{item.label}</Radio>
</List.Item>
)}></List>
</Radio.Group>
{/* <Select onChange={value => guestList_OnChange(value)} options={guestList_select()} placeholder="如果列表里面没有客人信息,请手动录到备注里" /> */}
</Form.Item>
</>
)}
<Form.Item label="客人信息/备注" name="Memo">
<Input.TextArea rows={4} disabled={isTicketType} />
</Form.Item>
<Form.Item name="CLF_SN" hidden>
<input />
</Form.Item>
<Form.Item name="GRI_SN" hidden>
<input />
</Form.Item>
<Form.Item name="VEI_SN" hidden>
<input />
</Form.Item>
<Form.Item name="CLC_SN" hidden>
<input />
</Form.Item>
</Form>
</Modal>
</Space>
);
};
export default TrainticketPlan;

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save