diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 4e8321a..7ef228f 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -19,7 +19,7 @@ "Back": "Back", "Download": "Download", "Upload": "Upload", - "preview": "Preview", + "Preview": "Preview", "Total": "Total", "Action": "Action", "Import": "Import", diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 20507cb..c2f8bbf 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -4,6 +4,7 @@ "ContractRemarks": "合同备注", "versionHistory": "Version History", "versionPublished": "Published", + "versionSnapshot": "Snapshot", "type": { "Experience": "Experience", "Car": "Transport Services", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 0a57df9..8c15a37 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -19,7 +19,7 @@ "Back": "返回", "Download": "下载", "Upload": "上传", - "preview": "预览", + "Preview": "预览", "Total": "总数", "Action": "操作", "Import": "导入", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 7bfe0e1..02b67ba 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -1,8 +1,10 @@ { "ProductType": "项目类型", + "ProductName": "产品名称", "ContractRemarks": "合同备注", "versionHistory": "查看历史", "versionPublished": "已发布的", + "versionSnapshot": "快照", "type": { "Experience": "综费", "Car": "车费", @@ -84,7 +86,8 @@ "withQuote": "是否复制报价", "requiredVendor": "请选择目标供应商", "requiredTypes": "请选择产品类型", - "requiredDept": "请选择所属小组" + "requiredDept": "请选择所属小组", + "copyTo": "复制到" }, "Validation": { "adultPrice": "请输入成人价", diff --git a/src/components/ProductsSelector.jsx b/src/components/ProductsSelector.jsx new file mode 100644 index 0000000..a3d79cf --- /dev/null +++ b/src/components/ProductsSelector.jsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react'; +import { Select, Spin } from 'antd'; +import { fetchJSON } from '@/utils/request'; +import { HT_HOST } from '@/config'; +import { useTranslation } from 'react-i18next'; +import { groupBy } from '@/utils/commons'; + +// 产品列表 +export const fetchAgencyProductsList = async (params) => { + const map = { title: 'label', id: 'value' }; + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel_agency_products`, params); + const byTypes = errcode !== 0 ? {} : (groupBy(result.products, (row) => row.info.product_type_name)); + // console.log(byTypes) + return Object.keys(byTypes).map((type_name) => ({ lable: type_name, title: type_name, key: type_name, options: byTypes[type_name].map(row => ({...row, label: `${row.info.code} : ${row.info.title}`, value: row.info.id})) })); +}; + +const ProductsSelector = ({ params, ...props }) => { + const { t } = useTranslation(); + const [fetching, setFetching] = useState(false); + const [options, setOptions] = useState([]); + + const fetchAction = async () => { + setOptions([]); + setFetching(true); + const data = await fetchAgencyProductsList(params); + // console.log(data) + setOptions(data); + setFetching(false); + return data; + }; + useEffect(() => { + fetchAction(); + + return () => {}; + }, []); + + return ( + <> + + + ); +}; +export default ProductsSelector; diff --git a/src/hooks/useProductsFormat.js b/src/hooks/useProductsFormat.js new file mode 100644 index 0000000..91dfe7e --- /dev/null +++ b/src/hooks/useProductsFormat.js @@ -0,0 +1,139 @@ +import { flush, groupBy, isEmpty, isNotEmpty, unique, uniqWith } from '@/utils/commons'; +import dayjs from 'dayjs'; +// Shoulder Season 平季; peak season 旺季 +const isFullYearOrLonger = (year, startDate, endDate) => { + // Parse the dates + const start = dayjs(startDate, 'YYYY-MM-DD'); + const end = dayjs(endDate, 'YYYY-MM-DD'); + + // Create the start and end dates for the year + const yearStart = dayjs(`${year}-01-01`, 'YYYY-MM-DD'); + const yearEnd = dayjs(`${year}-12-31`, 'YYYY-MM-DD'); + + // Check if start is '01-01' and end is '12-31' and the year matches + const isFullYear = start.isSame(yearStart, 'day') && end.isSame(yearEnd, 'day'); + + // Check if the range is longer than a year + const isLongerThanYear = end.diff(start, 'year') >= 1; + + return isFullYear || isLongerThanYear; +}; + +const uniqueBySub = (arr) => + arr.filter((subArr1, _, self) => { + return !self.some((subArr2) => { + if (subArr1 === subArr2) return false; // don't compare a subarray with itself + const set1 = new Set(subArr1); + const set2 = new Set(subArr2); + // check if subArr1 is a subset of subArr2 + return [...set1].every((value) => set2.has(value)); + }); + }); +export const chunkBy = (use_year, dataList = [], by = []) => { + const dataRollSS = dataList.map((rowp, ii) => { + const quotation = rowp.quotation.map((quoteItem) => { + return { + ...quoteItem, + quote_season: isFullYearOrLonger(use_year, quoteItem.use_dates_start, quoteItem.use_dates_end) ? 'SS' : 'PS', + }; + }); + return { ...rowp, quotation }; + }); + + // 人等分组只取平季, 因为产品只一行 + const allQuotesSS = dataRollSS.reduce((acc, rowp) => acc.concat(rowp.quotation.filter((q) => q.quote_season === 'SS')), []); + + const allQuotesPS = dataRollSS.reduce((acc, rowp) => acc.concat(rowp.quotation.filter((q) => q.quote_season === 'PS')), []); + const allQuotesSSS = isEmpty(allQuotesSS) ? allQuotesPS : allQuotesSS; + + const PGroupSizeSS = allQuotesSSS.reduce((aq, cq) => { + aq[cq.WPI_SN] = aq[cq.WPI_SN] || []; + aq[cq.WPI_SN].push(cq.group_size_min); + aq[cq.WPI_SN] = unique(aq[cq.WPI_SN]); + aq[cq.WPI_SN] = aq[cq.WPI_SN].slice().sort((a, b) => a - b); + return aq; + }, {}); + + const maxGroupSize = Math.max(...allQuotesSSS.map((q) => q.group_size_max)); + const maxSet = maxGroupSize === 1000 ? Infinity : maxGroupSize; + + const _SSMinSet = uniqWith(Object.values(PGroupSizeSS), (a, b) => a.join(',') === b.join(',')); + // const uSSsizeSetArr = (_SSMinSet) + const uSSsizeSetArr = uniqueBySub(_SSMinSet); + + // * 若不重叠分组, 则上面不要 uniqueBySub + for (const key in PGroupSizeSS) { + if (Object.prototype.hasOwnProperty.call(PGroupSizeSS, key)) { + const element = PGroupSizeSS[key]; + const findSet = uSSsizeSetArr.find((minCut) => element.every((v) => minCut.includes(v))); + PGroupSizeSS[key] = findSet; + } + } + + const [SSsizeSets, PSsizeSets] = [uSSsizeSetArr, []].map((arr) => { + const _arr = structuredClone(arr); + const arrSets = _arr.map((keyMins) => + keyMins.reduce((acc, curr, idx, minsArr) => { + const _max = idx === minsArr.length - 1 ? maxSet : Number(minsArr[idx + 1]) - 1; + acc.push([Number(curr), _max]); + return acc; + }, []) + ); + return arrSets; + }); + + const compactSizeSets = { + SSsizeSetKey: uSSsizeSetArr.map((s) => s.join(',')).filter(isNotEmpty), + sizeSets: SSsizeSets, + }; + + const chunkSS = structuredClone(dataRollSS).map((rowp) => { + const pkey = (PGroupSizeSS[rowp.info.id] || []).join(',') || compactSizeSets.SSsizeSetKey[0]; // todo: + + const thisRange = (PGroupSizeSS[rowp.info.id] || []).reduce((acc, curr, idx, minsArr) => { + const _max = idx === minsArr.length - 1 ? maxSet : Number(minsArr[idx + 1]) - 1; + acc.push([Number(curr), _max]); + return acc; + }, []); + const _quotation = rowp.quotation.map((quoteItem) => { + const ssSets = isEmpty(thisRange) ? SSsizeSets[0] : structuredClone(thisRange).reverse(); + + const matchRange = ssSets.find((ss) => quoteItem.group_size_min >= ss[0] && quoteItem.group_size_max <= ss[1]); + const findEnd = matchRange || ssSets.find((ss) => quoteItem.group_size_max > ss[0] && quoteItem.group_size_max <= ss[1] && ss[1] !== Infinity); + const findStart = findEnd || ssSets.find((ss) => quoteItem.group_size_min >= ss[0]); + const finalRange = findStart || ssSets[0]; + + quoteItem.quote_size = finalRange.join('-'); + return quoteItem; + }); + const quote_chunk_flat = groupBy(_quotation, (quoteItem2) => by.map((key) => quoteItem2[key]).join('@')); + const quote_chunk = Object.keys(quote_chunk_flat).reduce((qc, ckey) => { + const ckeyArr = ckey.split('@'); + if (isEmpty(qc[ckeyArr[0]])) { + qc[ckeyArr[0]] = ckeyArr[1] ? { [ckeyArr[1]]: quote_chunk_flat[ckey] } : quote_chunk_flat[ckey]; + } else { + qc[ckeyArr[0]][ckeyArr[1]] = (qc[ckeyArr[0]][ckeyArr[1]] || []).concat(quote_chunk_flat[ckey]); + } + return qc; + }, {}); + return { + ...rowp, + sizeSetsSS: pkey, + quotation: _quotation, + quote_chunk, + }; + }); + + const allquotation = chunkSS.reduce((a, c) => a.concat(c.quotation), []); + // 取出两季相应的时效区间 + const SSRange = unique((allquotation || []).filter((q) => q.quote_season === 'SS').map((qr) => `${qr.use_dates_start}~${qr.use_dates_end}`)); + const PSRange = unique((allquotation || []).filter((q) => q.quote_season === 'PS').map((qr) => `${qr.use_dates_start}~${qr.use_dates_end}`)); + + return { + chunk: chunkSS, + dataSource: chunkSS, + SSRange, + PSRange, + ...compactSizeSets, + }; +}; diff --git a/src/hooks/useProductsQuotationFormat.js b/src/hooks/useProductsQuotationFormat.js new file mode 100644 index 0000000..254daf7 --- /dev/null +++ b/src/hooks/useProductsQuotationFormat.js @@ -0,0 +1,395 @@ +import { flush, groupBy, isEmpty, isNotEmpty, pick, unique, uniqWith } from '@/utils/commons'; +import dayjs from 'dayjs'; +import { formatGroupSize } from './useProductsSets'; + +// Shoulder Season 平季; peak season 旺季 +export const isFullYearOrLonger = (year, startDate, endDate) => { + // Parse the dates + const start = dayjs(startDate, 'YYYY-MM-DD'); + const end = dayjs(endDate, 'YYYY-MM-DD'); + + // Create the start and end dates for the year + const yearStart = dayjs(`${year}-01-01`, 'YYYY-MM-DD'); + const yearEnd = dayjs(`${year}-12-31`, 'YYYY-MM-DD'); + + // Check if start is '01-01' and end is '12-31' and the year matches + const isFullYear = start.isSame(yearStart, 'day') && end.isSame(yearEnd, 'day'); + + // Check if the range is longer than a year + const isLongerThanYear = end.diff(startDate, 'year') >= 1; + const isLongerThan12M = end.diff(startDate, 'month') >= 11; + + return isFullYear || isLongerThanYear || isLongerThan12M; +}; + +const uniqueBySub = (arr) => { + const sortedArr = arr.sort((a, b) => b.length - a.length); + const uniqueArr = []; + sortedArr.forEach((currentSubArr) => { + const isSubsetOfUnique = uniqueArr.some((uniqueSubArr) => { + return currentSubArr.every((item) => uniqueSubArr.includes(item)); + }); + if (!isSubsetOfUnique) { + uniqueArr.push(currentSubArr); + } + }); + + return uniqueArr; +} + +export const chunkBy = (use_year, dataList = [], by = []) => { + const dataRollSS = dataList.map((rowp, ii) => { + const quotation = rowp.quotation.map((quoteItem) => { + return { + ...quoteItem, + quote_season: isFullYearOrLonger(use_year, quoteItem.use_dates_start, quoteItem.use_dates_end) ? 'SS' : 'PS', + }; + }); + return { ...rowp, quotation }; + }); + + // 人等分组只取平季, 因为产品只一行 + const allQuotesSS = dataRollSS.reduce((acc, rowp) => acc.concat(rowp.quotation.filter((q) => q.quote_season === 'SS')), []); + + const allQuotesPS = dataRollSS.reduce((acc, rowp) => acc.concat(rowp.quotation.filter((q) => q.quote_season === 'PS')), []); + const allQuotesSSS = isEmpty(allQuotesSS) ? allQuotesPS : allQuotesSS; + + const allQuotesSSS2 = [].concat(allQuotesSS, allQuotesPS); + + const PGroupSizeSS = allQuotesSSS.reduce((aq, cq) => { + aq[cq.WPI_SN] = aq[cq.WPI_SN] || []; + aq[cq.WPI_SN].push(`${cq.group_size_min}-${cq.group_size_max}`); + // aq[cq.WPI_SN].push([cq.group_size_min, cq.group_size_max]); + // aq[cq.WPI_SN].push(cq.group_size_min); + aq[cq.WPI_SN] = unique(aq[cq.WPI_SN]); + aq[cq.WPI_SN] = aq[cq.WPI_SN].slice().sort((a, b) => a.split('-')[0] - b.split('-')[0]); + return aq; + }, {}); + // debug: + // PGroupSizeSS['5098'] = ['1-1000']; + // PGroupSizeSS['5099'] = ['1-2', '3-4']; + + const PGroupSizePS = allQuotesPS.reduce((aq, cq) => { + aq[cq.WPI_SN] = aq[cq.WPI_SN] || []; + aq[cq.WPI_SN].push(`${cq.group_size_min}-${cq.group_size_max}`); + // aq[cq.WPI_SN].push([cq.group_size_min, cq.group_size_max]); + // aq[cq.WPI_SN].push(cq.group_size_min); + aq[cq.WPI_SN] = unique(aq[cq.WPI_SN]); + aq[cq.WPI_SN] = aq[cq.WPI_SN].slice().sort((a, b) => a.split('-')[0] - b.split('-')[0]); + return aq; + }, {}); + + // 补全产品旺季的人等分组 (当旺季和平季的人等不完全一致时) + const allWPI = unique(allQuotesSSS2.map((ele) => ele.WPI_SN)); + for (const WPI of allWPI) { + // for (const WPI in PGroupSizeSS) { + // if (Object.prototype.hasOwnProperty.call(PGroupSizeSS, WPI)) { + const element = PGroupSizeSS[WPI] || []; + const elementP = PGroupSizePS[WPI] || []; + const diff = (elementP || []).filter((ele, index) => !element.includes(ele)); + PGroupSizeSS[WPI] = element.concat(diff); + // } + } + + // console.log('PGroupSizeSS', PGroupSizeSS, '\nPGroupSizePS', PGroupSizePS, '\nallQuotesSSS', allQuotesSSS2) + + // const maxGroupSize = Math.max(...allQuotesSSS.map((q) => q.group_size_max)); + // const maxSet = maxGroupSize === 1000 ? Infinity : maxGroupSize; + + const _SSMinSet = uniqWith(Object.values(PGroupSizeSS), (a, b) => a.join(',') === b.join(',')); + // const uSSsizeSetArr = (_SSMinSet) + const uSSsizeSetArr = uniqueBySub(_SSMinSet); + // console.log('_SSMinSet', _SSMinSet, '\n uSSsizeSetArr', uSSsizeSetArr) + + // * 若不重叠分组, 则上面不要 uniqueBySub + for (const key in PGroupSizeSS) { + if (Object.prototype.hasOwnProperty.call(PGroupSizeSS, key)) { + const element = PGroupSizeSS[key]; + const findSet = uSSsizeSetArr.find((minCut) => element.every((v) => minCut.includes(v))); + PGroupSizeSS[key] = findSet; + } + } + // console.log('PGroupSizeSS -- ', PGroupSizeSS) + + const [SSsizeSets, PSsizeSets] = [uSSsizeSetArr, []].map((arr) => { + const _arr = structuredClone(arr); + const arrSets = _arr.map((keyMinMaxStrs) => + keyMinMaxStrs.reduce((acc, curr, idx, minMaxArr) => { + const curArr = curr.split('-').map(val => parseInt(val, 10)); + acc.push(curArr); + // const _max = idx === minsArr.length - 1 ? maxSet : Number(minsArr[idx + 1]) - 1; + // acc.push([Number(curr), _max]); + return acc; + }, []) + ); + return arrSets; + }); + + // console.log('uSSsizeSetArr', uSSsizeSetArr); + const [SSsizeSetsMap, PSsizeSetsMap] = [uSSsizeSetArr, []].map((arr) => { + const _arr = structuredClone(arr); + const SetsMap = _arr.reduce((acc, keyMinMaxStrs, ii, strArr) => { + const _key = keyMinMaxStrs.join(','); + // console.log(_key); + const _value = keyMinMaxStrs.reduce((acc, curr, idx, minMaxArr) => { + const curArr = curr.split('-').map((val) => parseInt(val, 10)); + acc.push(curArr); + return acc; + }, []); + return { ...acc, [_key]: _value }; + }, {}); + return SetsMap; + }); + // console.log('SSsizeSetsMap', SSsizeSetsMap); + + const compactSizeSets = { + SSsizeSetKey: uSSsizeSetArr.map((s) => s.join(',')).filter(isNotEmpty), + sizeSets: SSsizeSets, + SSsizeSetsMap, + }; + // console.log('sizeSets -- ', SSsizeSets, '\nSSsizeSetKey', compactSizeSets.SSsizeSetKey, '\nSSsizeSetsMap', SSsizeSetsMap) + + const chunkSS = structuredClone(dataRollSS).map((rowp) => { + const pkey = (PGroupSizeSS[rowp.info.id] || []).join(',') || compactSizeSets.SSsizeSetKey[0]; // todo: + + let unitCnt = { '0': 0, '1': 0 }; // ? todo: 以平季的为准 + const _quotation = rowp.quotation.map((quoteItem) => { + unitCnt[quoteItem.unit_id]++; + + quoteItem.quote_size = pkey; + quoteItem.quote_col_key = formatGroupSize(quoteItem.group_size_min, quoteItem.group_size_max); + quoteItem.use_dates_start = quoteItem.use_dates_start.replace(/-/g, '.'); + quoteItem.use_dates_end = quoteItem.use_dates_end.replace(/-/g, '.'); + return quoteItem; + }); + const quote_chunk_flat = groupBy(_quotation, (quoteItem2) => by.map((key) => quoteItem2[key]).join('@') || '#'); + const quote_chunk = Object.keys(quote_chunk_flat).reduce((qc, ckey) => { + const ckeyArr = ckey.split('@'); + if (isEmpty(qc[ckeyArr[0]])) { + qc[ckeyArr[0]] = ckeyArr[1] ? { [ckeyArr[1]]: quote_chunk_flat[ckey] } : quote_chunk_flat[ckey]; + } else { + qc[ckeyArr[0]][ckeyArr[1]] = (qc[ckeyArr[0]][ckeyArr[1]] || []).concat(quote_chunk_flat[ckey]); + } + return qc; + }, {}); + + const _quotationTransposeBySize = Object.keys(quote_chunk).reduce((accBy, byKey) => { + const byValues = quote_chunk[byKey]; + const groupTablesBySize = groupBy(byValues, 'quote_size'); + const transposeTables = Object.keys(groupTablesBySize).reduce((accBy, sizeKeys) => { + const _sizeRows = groupTablesBySize[sizeKeys]; + const rowsByDate = groupBy(_sizeRows, qi => `${qi.use_dates_start}~${qi.use_dates_end}`); + const _rowsFromDate = Object.keys(rowsByDate).reduce((accDate, dateKeys) => { + const _dateRows = rowsByDate[dateKeys]; + const rowKey = _dateRows.map(e => e.id).join(','); + const keepCol = pick(_dateRows[0], ['WPI_SN', 'WPP_VEI_SN', 'currency', 'unit_id', 'unit_name', 'use_dates_start', 'use_dates_end', 'weekdays', 'quote_season']); + const _colFromDateRow = _dateRows.reduce((accCols, rowp) => { + // const _colRow = pick(rowp, ['currency', 'unit_id', 'unit_name', 'use_dates_start', 'use_dates_end', 'weekdays', 'child_cost', 'adult_cost']); + return { ...accCols, [rowp.quote_col_key]: rowp }; + }, {...keepCol, originRows: _dateRows, rowKey }); + accDate.push(_colFromDateRow); + return accDate; + }, []); + return { ...accBy, [sizeKeys]: _rowsFromDate }; + }, {}); + return { ...accBy, [byKey]: transposeTables }; + }, {}); + // console.log(_quotationTransposeBySize); + + return { + ...rowp, + unitCnt, + unitSet: Object.keys(unitCnt).reduce((a, b) => unitCnt[a] > unitCnt[b] ? a : b), + sizeSetsSS: pkey, + _quotationTransposeBySize, + quotation: _quotation, + quote_chunk, + }; + }); + + const allquotation = chunkSS.reduce((a, c) => a.concat(c.quotation), []); + // 取出两季相应的时效区间 + const SSRange = unique((allquotation || []).filter((q) => q.quote_season === 'SS').map((qr) => `${qr.use_dates_start}~${qr.use_dates_end}`)); + const PSRange = unique((allquotation || []).filter((q) => q.quote_season === 'PS').map((qr) => `${qr.use_dates_start}~${qr.use_dates_end}`)); + + // const transposeDataSS = chunkSS + + return { + chunk: chunkSS, + // dataSource: chunkSS, + SSRange, + PSRange, + ...compactSizeSets, // { SSsizeSetKey, sizeSets } + }; +}; + +/** + * 按[单位, 人等]拆分表格 + * @use D J B R 8 + */ +export const splitTable_SizeSets = (chunkData) => { + const { SSRange, PSRange, SSsizeSetKey, SSsizeSetsMap, chunk } = chunkData; + // console.log('---- chunk', chunk); + const bySizeUnitSetKey = groupBy(chunk, pitem => ['unitSet', 'sizeSetsSS', ].map((key) => pitem[key]).join('@')); + // agencyProducts.J. + // console.log('bySizeSetKey', bySizeUnitSetKey); + const tables = Object.keys(bySizeUnitSetKey).map((sizeSetsUnitStr) => { + const [unitSet, sizeSetsStr] = sizeSetsUnitStr.split('@'); + const _thisSSsetProducts = bySizeUnitSetKey[sizeSetsUnitStr]; + const _subTable = _thisSSsetProducts.map(({ info, sizeSetsSS, _quotationTransposeBySize, unitSet, ...pitem }) => { + const transpose = _quotationTransposeBySize['#'][sizeSetsSS]; + const _pRow = transpose.map((quote, qi) => ({ ...quote, rowSpan: qi === 0 ? transpose.length : 0 })); + return { info, sizeSetsSS, unitSet, rows: _pRow, transpose }; + }); + return { cols: SSsizeSetsMap[sizeSetsStr], colsKey: sizeSetsStr, unitSet, sizeSetsUnitStr, data: _subTable }; + }); + // console.log('---- tables', tables); + const tablesQuote = tables.map(({ cols, colsKey, unitSet, sizeSetsUnitStr, data }, ti) => { + const _table = data.reduce((acc, prow) => { + const prows = prow.rows.map((_q) => ({ ..._q, info: prow.info, dateText: `${_q.use_dates_start}~${_q.use_dates_end}` })); + return acc.concat(prows); + }, []); + return { cols, colsKey: sizeSetsUnitStr, data: _table }; // `${unitSet}@${colsKey}` + }); + // console.log('---- tablesQuote', tablesQuote); + return tablesQuote; +}; + +/** + * 按季度分列 [平季, 旺季] + * @use Q 7 6 + */ +export const splitTable_Season = (chunkData) => { + const { SSRange, PSRange, SSsizeSetKey, SSsizeSetsMap, chunk } = chunkData; + // console.log(chunkData); + const tablesQuote = chunk.map((pitem) => { + const { quote_chunk } = pitem; + // const bySeason = groupBy(pitem.quotation, (ele) => ele.quote_season); + const rowSeason = Object.keys(quote_chunk).reduce((accp, _s) => { + const bySeasonValue = groupBy(quote_chunk[_s], (ele) => ['adult_cost', 'child_cost', 'group_size_min', 'group_size_max', 'unit_id'].map((k) => ele[k]).join('@')); + // console.log('---- bySeasonValue', _s, bySeasonValue); + + const byDate = groupBy(quote_chunk[_s], (ele) => `${ele.use_dates_start}~${ele.use_dates_end}`); + // console.log('---- byDate', _s, byDate); + + const subHeader = Object.keys(bySeasonValue).length >= Object.keys(byDate).length ? 'dates' : 'priceValues'; + // console.log('---- subHeader', _s, subHeader); + + let valuesArr = []; + switch (subHeader) { + case 'priceValues': + valuesArr = Object.keys(bySeasonValue).reduce((accv, valKey) => { + const valRows = bySeasonValue[valKey]; + const valRow = pick(valRows[0], ['adult_cost', 'child_cost', 'currency', 'unit_id', 'unit_name', 'group_size_min', 'group_size_max']); + // valRow.dates = valRows.map((v) => pick(v, ['id', 'use_dates_end', 'use_dates_start'])); + valRow.rows = [valRows[0]]; + valRow.originRows = valRows; + valRow.rowKey = valRows.map((v) => v.id).join(','); + valRow.headerDates = valRows.map((v) => pick(v, ['use_dates_end', 'use_dates_start'])); + accv.push(valRow); + return accv; + }, []); + break; + case 'dates': + valuesArr = Object.keys(byDate).reduce((accv, dateKey) => { + const valRows = byDate[dateKey]; + const valRow = pick(valRows[0], ['use_dates_end', 'use_dates_start']); + valRow.rows = valRows; + valRow.originRows = valRows; + valRow.rowKey = valRows.map((v) => v.id).join(','); + valRow.headerDates = [pick(valRows[0], ['use_dates_end', 'use_dates_start'])]; + accv.push(valRow); + return accv; + }, []); + break; + + default: + break; + } + + const valUnderSeason = Object.keys(bySeasonValue).reduce((accv, valKey) => { + const valRows = bySeasonValue[valKey]; + const valRow = pick(valRows[0], ['adult_cost', 'child_cost', 'currency', 'unit_id', 'unit_name', 'group_size_min', 'group_size_max']); + // valRow.dates = valRows.map((v) => pick(v, ['id', 'use_dates_end', 'use_dates_start'])); + valRow.rows = valRows; + valRow.rowKey = valRows.map(v => v.id).join(','); + accv.push(valRow); + return accv; + }, []); + + return { ...accp, [_s]: valUnderSeason, [_s + 'Data']: valuesArr }; + }, {}); + return { info: pitem.info, ...rowSeason, rowKey: pitem.info.id }; + }); + // console.log('---- tablesQuote', tablesQuote); + return tablesQuote; +}; + +export const splitTable_D = (use_year, dataSource, retTableOnly = true) => { + const chunked = chunkBy(use_year, dataSource); + // console.log(chunked); + const tables = addCityRow4Split(splitTable_SizeSets(chunked)); + return retTableOnly ? tables : { ...chunked, tables }; +}; + +export const splitTable_J = (use_year, dataSource, retTableOnly = true) => { + const chunked = chunkBy(use_year, dataSource); + // console.log(chunked); + const tables = addCityRow4Split(splitTable_SizeSets(chunked)); + return retTableOnly ? tables : { ...chunked, tables }; +}; + +export const splitTable_Q = (use_year, dataSource) => { + const chunked = chunkBy(use_year, dataSource, ['quote_season']); + return addCityRow4Season(splitTable_Season(chunked)); +}; + +export const splitTable_7 = (use_year, dataSource) => { + const chunked = chunkBy(use_year, dataSource, ['quote_season']); + return addCityRow4Season(splitTable_Season(chunked)); +}; + +export const splitTable_R = (use_year, dataSource, retTableOnly = true) => { + const chunked = chunkBy(use_year, dataSource); + // console.log(chunked); + const tables = addCityRow4Split(splitTable_SizeSets(chunked)); + return retTableOnly ? tables : { ...chunked, tables }; +}; + +export const splitTable_8 = (use_year, dataSource, retTableOnly = true) => { + const chunked = chunkBy(use_year, dataSource); + // console.log(chunked); + const tables = addCityRow4Split(splitTable_SizeSets(chunked)); + return retTableOnly ? tables : { ...chunked, tables }; +}; + +export const splitTable_6 = (use_year, dataSource, retTableOnly = true) => { + const chunked = chunkBy(use_year, dataSource, ['quote_season']); + const tables = splitTable_Season(chunked); + return retTableOnly ? tables : { ...chunked, tables }; +}; + +export const splitTable_B = (use_year, dataSource, retTableOnly = true) => { + const chunked = chunkBy(use_year, dataSource); + // console.log(chunked); + const tables = addCityRow4Split(splitTable_SizeSets(chunked)); + return retTableOnly ? tables : { ...chunked, tables }; +}; + +export const addCityRow4Season = (table) => { + const byCity = groupBy(table, (ele) => `${ele.info.city_id}@${ele.info.city_name}`); + const withCityRow = Object.keys(byCity).reduce((acc, cityIdName) => { + const [cityId, cityName] = cityIdName.split('@'); + acc.push({ info: { product_title: cityName, isCityRow: true,}, use_dates_end: '', use_dates_start: '', quote_season: 'SS', rowSpan: 1, rowKey: `c_${cityId}` }); + return acc.concat(byCity[cityIdName]); + }, []); + return withCityRow; +}; + +export const addCityRow4Split = (splitTables) => { + const tables = splitTables.map(table => { + return { ...table, data: addCityRow4Season(table.data)} + }); + return tables; +}; + diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index d960e91..4f5e334 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -70,6 +70,9 @@ export const useProductsTypesMapVal = (value) => { return stateMapVal; }; +/** + * 价格的审核状态 + */ export const useProductsAuditStates = () => { const [types, setTypes] = useState([]); const { t, i18n } = useTranslation(); @@ -89,7 +92,7 @@ export const useProductsAuditStates = () => { return types; }; -export const useProductsAuditStatesMapVal = (value) => { +export const useProductsAuditStatesMapVal = () => { const stateSets = useProductsAuditStates(); const stateMapVal = stateSets.reduce((r, c) => ({ ...r, [`${c.value}`]: c }), {}); return stateMapVal; @@ -153,7 +156,7 @@ export const useNewProductRecord = () => { 'lastedit_changed': '', 'create_date': '', 'created_by': '', - 'edit_status': 2, + 'edit_status': 2, // 信息的审核状态 1已发布,2已审核 'sort_order': '', 'sub_type_D': '', // 包价类型, 值保存在`item_type`字段中 'item_type': '', // 产品子类型的值 @@ -206,6 +209,6 @@ export const PackageTypes = [ { key: '35014', value: '35014', label: '其它(餐补等)' }, ]; -export const formatGroupSize = (min, max) => { - return max === 1000 ? min === 0 ? '不分人等' : `${min}人以上` : `${min}-${max}`; +export const formatGroupSize = (min, max, suffix = false) => { + return max === 1000 ? min <= 1 ? '不分人等' : `${min}人以上` : (`${min}-${max}`+(suffix ? '人' : '')); }; diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index d93de56..a08c6e9 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -132,20 +132,23 @@ const PriceTable = ({ productType, dataSource, refresh }) => { // title: '', // key: 'action2', // width: '6rem', - // className: 'bg-white', + // className: 'bg-white align-bottom', // onCell: (r, index) => ({ rowSpan: r.rowSpan }), // render: (_, r) => { // const showPublicBtn = null; // r.pendingQuotation ? : null; - // const btn2 = r.showPublicBtn ? ( - // + // + // // ) : null; - // return
{btn2}
; + // return
{btn2}
; // }, // }, ]; diff --git a/src/views/products/Detail/CopyProducts.jsx b/src/views/products/Detail/CopyProducts.jsx index 40fbca5..feccf17 100644 --- a/src/views/products/Detail/CopyProducts.jsx +++ b/src/views/products/Detail/CopyProducts.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { App, Form, Modal, DatePicker, Divider, Switch } from 'antd'; +import { App, Form, Modal, DatePicker, Divider, Switch, Space, Flex } from 'antd'; import { isEmpty, objectMapper } from '@/utils/commons'; import { useTranslation } from 'react-i18next'; @@ -13,43 +13,102 @@ import { copyAgencyDataAction } from '@/stores/Products/Index'; import useAuthStore from '@/stores/Auth'; import RequireAuth from '@/components/RequireAuth'; import { PERM_PRODUCTS_MANAGEMENT } from '@/config'; +import ProductsSelector from '@/components/ProductsSelector'; dayjs.extend(arraySupport); export const CopyProductsForm = ({ action, initialValues, onFormInstanceReady, source, ...props }) => { const { t } = useTranslation(); const [form] = Form.useForm(); + const { + sourceAgency: { travel_agency_id }, + sourceYear: use_year, + } = source; const isPermitted = useAuthStore((state) => state.isPermitted); + const [typeDisabled, setTypeDisabled] = useState(false); useEffect(() => { onFormInstanceReady(form); }, []); const onValuesChange = (changeValues, allValues) => {}; + return ( -
- {action === '#' && - - } - - - - {action === '#' && - - + + +
+ {action === '#' && ( + + + + + + )} + ({ + warningOnly: true, + validator: async () => { + if (!isEmpty(getFieldValue('products_list'))) { + // setTypeDisabled(true); + return Promise.reject(new Error(t(`⚠勾选了复制的产品名称之后 🔽, 将忽略选择的类型 🔼`))); + } + // setTypeDisabled(false); + return Promise.resolve(); + }, + }), + ]}> + + + + + + {t('products:CopyFormMsg.copyTo')}: + {action === '#' && ( + + + + )} + + + + + + + + + + {/* disabledDate={(current) => current <= dayjs([source.sourceYear, 12, 31])} */} + + + + + + +
+ + + {() => ( +
+ {!isEmpty(form.getFieldValue('products_list')) && 已选择的产品 预览:} + {(form.getFieldValue('products_list') || []).map((item, index) => ( +
+ {index + 1}. {item.label} +
+ ))} +
+ )}
-
} - - - - - - {/* disabledDate={(current) => current <= dayjs([source.sourceYear, 12, 31])} */} - - - - +
); }; @@ -76,9 +135,10 @@ const formValuesMapper = (values) => { }, }, 'with_quote': { key: 'with_quote', transform: (value) => (value ? 1 : 0) }, + 'products_list': { key: 'product_id_list', transform: (value) => (Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : '') }, }; let dest = {}; - const { agency, year, ...omittedValue } = values; + const { agency, year, products_list, ...omittedValue } = values; dest = { ...omittedValue, ...objectMapper(values, destinationObject) }; for (const key in dest) { if (Object.prototype.hasOwnProperty.call(dest, key)) { @@ -101,15 +161,18 @@ export const CopyProductsFormModal = ({ source, action = '#' | 'o', open, onSubm const handleCopyAgency = async (param) => { param.target_agency = isEmpty(param.target_agency) ? source.sourceAgency.travel_agency_id : param.target_agency; setCopyLoading(true); - // console.log(param); + // debug: + // console.log('ready params', param); + // setCopyLoading(false); + // throw new Error('暂不支持复制'); // const toID = param.target_agency; - const success = await copyAgencyDataAction({...param, source_agency: source.sourceAgency.travel_agency_id}).catch(ex => { + const success = await copyAgencyDataAction({ ...param, source_agency: source.sourceAgency.travel_agency_id }).catch((ex) => { notification.error({ message: 'Notification', description: ex.message, placement: 'top', duration: 4, - }) + }); }); setCopyLoading(false); success ? message.success(t('Success')) : message.error(t('Failed')); @@ -122,7 +185,7 @@ export const CopyProductsFormModal = ({ source, action = '#' | 'o', open, onSubm }; return ( - { diff --git a/src/views/products/Detail/Header.jsx b/src/views/products/Detail/Header.jsx index 5d0cce9..4d0f0cf 100644 --- a/src/views/products/Detail/Header.jsx +++ b/src/views/products/Detail/Header.jsx @@ -7,22 +7,18 @@ 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 { usingStorage } from "@/hooks/usingStorage"; -import AgencyContract from "../Print/AgencyContract"; -// import AgencyContract from "../Print/AgencyContract_v0903"; -import { saveAs } from "file-saver"; -import { Packer } from "docx"; +import AgencyPreview from "../Print/AgencyPreview"; +import ExportDocxBtn from "../Print/ExportDocxBtn"; const Header = ({ refresh, ...props }) => { const location = useLocation(); @@ -42,7 +38,6 @@ const Header = ({ refresh, ...props }) => { state.setSwitchParams, ]); // const [activeAgencyState] = useProductsStore((state) => [state.activeAgencyState]); - const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]); const stateMapVal = useProductsAuditStatesMapVal(); const { message, notification } = App.useApp(); const navigate = useNavigate(); @@ -56,10 +51,6 @@ const Header = ({ refresh, ...props }) => { yearOptions.push({ label: i, value: i }); } - const { getRemarkList } = useProductsStore((selector) => ({ - getRemarkList: selector.getRemarkList, - })); - const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id, @@ -166,28 +157,6 @@ const Header = ({ refresh, ...props }) => { }); }; - 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, - 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` - ); - }); - }; - return (
@@ -235,13 +204,8 @@ const Header = ({ refresh, ...props }) => { />
- {/* todo: export, 审核完成之后才能导出 */} - - - {/* */} - + + {/* {activeAgencyState === 0 && ( */} <> diff --git a/src/views/products/Detail/ProductInfoQuotation.jsx b/src/views/products/Detail/ProductInfoQuotation.jsx index 853e459..47cd2e0 100644 --- a/src/views/products/Detail/ProductInfoQuotation.jsx +++ b/src/views/products/Detail/ProductInfoQuotation.jsx @@ -13,6 +13,7 @@ import { DatePicker, Space, App, + Popconfirm, Tooltip, } from "antd"; import { useTranslation } from "react-i18next"; @@ -20,7 +21,6 @@ import { CloseOutlined, StarTwoTone, PlusOutlined, - ExclamationCircleFilled, QuestionCircleOutlined, } from "@ant-design/icons"; import { useDatePresets } from "@/hooks/useDatePresets"; @@ -179,7 +179,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => { const [isBatchSetupModalOpen, setBatchSetupModalOpen] = useState(false); const [groupAllSize, setGroupAllSize] = useState(false); const [groupMaxUnlimit, setGroupMaxUnlimit] = useState(false); - const { modal, notification } = App.useApp(); + const { notification } = App.useApp(); const [quotationForm] = Form.useForm(); const [batchSetupForm] = Form.useForm(); @@ -206,6 +206,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => { }; const onNewQuotation = () => { + setGroupAllSize(false); // 新报价不分人等 const emptyQuotation = newEmptyQuotation(defaultUseDates); quotationForm.setFieldsValue(emptyQuotation); setQuotationModalOpen(true); @@ -224,24 +225,6 @@ const ProductInfoQuotation = ({ editable, ...props }) => { setBatchSetupModalOpen(false); }; - const onDeleteQuotation = (quotation) => { - modal.confirm({ - title: "请确认", - icon: , - content: "你要删除这条价格吗?", - onOk() { - deleteQuotation(quotation).catch((ex) => { - notification.error({ - message: "Notification", - description: ex.message, - placement: "top", - duration: 4, - }); - }); - }, - }); - }; - const quotationColumns = [ // { title: 'id', dataIndex: 'id', width: 40, className: 'italic text-gray-400' }, // test: 0 // { title: 'WPI_SN', dataIndex: 'WPI_SN', width: 40, className: 'italic text-gray-400' }, // test: 0 @@ -256,13 +239,9 @@ const ProductInfoQuotation = ({ editable, ...props }) => { title: ( <> {t("products:unit_name")}{" "} - + - {" "} + ), dataIndex: "unit_id", @@ -283,15 +262,14 @@ const ProductInfoQuotation = ({ editable, ...props }) => { {t("products:use_dates")}{" "} - {" "} + ), dataIndex: "use_dates", - // width: '6rem', render: (_, record) => `${record.use_dates_start}-${record.use_dates_end}`, }, @@ -302,25 +280,37 @@ const ProductInfoQuotation = ({ editable, ...props }) => { dataIndex: "operation", width: "10rem", render: (_, quotation) => { - // const _rowEditable = [-1,3].includes(quotation.audit_state_id); - const _rowEditable = true; // test: 0 return ( - + + ); }, diff --git a/src/views/products/Detail/ProductQuotationLogPopover.jsx b/src/views/products/Detail/ProductQuotationLogPopover.jsx index cdbc813..99a7d5b 100644 --- a/src/views/products/Detail/ProductQuotationLogPopover.jsx +++ b/src/views/products/Detail/ProductQuotationLogPopover.jsx @@ -23,6 +23,14 @@ const getPPRunningAction = async (params) => { return errcode !== 0 ? [] : result; }; +/** + * 产品价格快照 + */ +const getPPSnapshotAction = async (params) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_snapshot`, params) + return errcode !== 0 ? [] : result; +} + const parseJson = (str) => { let result; if (str === null || str === undefined || str === '') { @@ -177,6 +185,15 @@ const useLogMethod = (method) => { return { data: data?.[0]?.quotation || [] }; }, }, + 'snapshot': { + title: '📷' + t('versionSnapshot'), + btnText: t('versionSnapshot'), + fetchData: async (params) => { + const { price_id, ..._params } = params; + const data = await getPPSnapshotAction(_params); + return data; //?.[0]?.quotation || []; + }, + }, }; return methodMap[method]; }; @@ -190,7 +207,7 @@ const useLogMethod = (method) => { * * @param {Object} props - Component props * @param {string} props.btnText - The text to display on the trigger button and in the popover header - * @param {'history' | 'published'} props.method - Determines data source - "history" for change logs or "published" for published quotations + * @param {'history' | 'published' | 'snapshot'} props.method - Determines data source - "history" for change logs or "published" for published quotations * @param {Object} props.triggerProps - Additional props to pass to the trigger button * @param {number} props.travel_agency_id - ID of the travel agency (used in data fetching) * @param {number} props.product_id - ID of the product (used in data fetching) diff --git a/src/views/products/Detail/ProductQuotationSnapshotPopover.jsx b/src/views/products/Detail/ProductQuotationSnapshotPopover.jsx new file mode 100644 index 0000000..8bae688 --- /dev/null +++ b/src/views/products/Detail/ProductQuotationSnapshotPopover.jsx @@ -0,0 +1,299 @@ +import { useState, useMemo } from 'react'; +import { Button, Table, Popover, Typography, List, Flex } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { HT_HOST } from '@/config'; +import { fetchJSON } from '@/utils/request'; +import { formatGroupSize } from '@/hooks/useProductsSets'; +import { isEmpty, isNotEmpty } from '@/utils/commons'; +import { chunkBy } from '@/hooks/useProductsQuotationFormat'; + +/** + * 产品价格日志 + */ +const getPPLogAction = async (params) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_log`, params) + return errcode !== 0 ? [] : result; +}; + +/** + * 产品价格: 已发布的 + */ +const getPPRunningAction = async (params) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_running`, params) + return errcode !== 0 ? [] : result; +}; + +/** + * 产品价格快照 + */ +const getPPSnapshotAction = async (params) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_snapshot`, params) + return errcode !== 0 ? [] : result; +} + +const parseJson = (str) => { + let result; + if (str === null || str === undefined || str === '') { + return {}; + } + try { + result = JSON.parse(str); + return Array.isArray(result) ? result.reduce((acc, cur) => ({ ...acc, ...cur }), {}) : result; + } catch (e) { + return {}; + } +}; + +export const columnsSets = (t, colorize = true) => [ + { + key: 'adult', + title: t('AgeType.Adult'), + width: '12rem', + render: (_, { adult_cost, currency, unit_id, unit_name, audit_state_id, lastedit_changed }) => { + const _changed = parseJson(lastedit_changed); + const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id); + const ifData = isNotEmpty(_changed.adult_cost) || isNotEmpty(_changed.unit_id) || isNotEmpty(_changed.currency); + const preValue = + ifCompare && ifData ? ( +
{`${_changed.adult_cost} ${_changed.currency || currency} / ${t(`PriceUnit.${_changed.unit_id || unit_id}`)}`}
+ ) : null; + const editCls = ifCompare && ifData ? 'text-danger' : ''; + return ( +
+ {preValue} + {`${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}`} +
+ ); + }, + }, + { + key: 'child', + title: t('AgeType.Child'), + width: '12rem', + render: (_, { child_cost, currency, unit_id, unit_name, audit_state_id, lastedit_changed }) => { + const _changed = parseJson(lastedit_changed); + const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id); + const ifData = isNotEmpty(_changed.child_cost) || isNotEmpty(_changed.unit_id) || isNotEmpty(_changed.currency); + const preValue = + ifCompare && ifData ? ( +
{`${_changed.child_cost} ${_changed.currency || currency} / ${t(`PriceUnit.${_changed.unit_id || unit_id}`)}`}
+ ) : null; + const editCls = ifCompare && ifData ? 'text-danger' : ''; + return ( +
+ {preValue} + {`${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}`} +
+ ); + }, + }, + // {key: 'unit', title: t('Unit'), }, + { + key: 'groupSize', + dataIndex: ['group_size_min'], + title: t('group_size'), + width: '6rem', + render: (_, { audit_state_id, group_size_min, group_size_max, lastedit_changed }) => { + const _changed = parseJson(lastedit_changed); + const preValue = + colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.group_size_min) || isNotEmpty(_changed.group_size_max)) ? ( +
{`${_changed.group_size_min ?? group_size_min} - ${_changed.group_size_max ?? group_size_max}`}
+ ) : null; + const editCls = colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.group_size_min) || isNotEmpty(_changed.group_size_max)) ? 'text-danger' : ''; + return ( +
+ {preValue} + {formatGroupSize(group_size_min, group_size_max)} +
+ ); + }, + }, + { + key: 'useDates', + dataIndex: ['use_dates_start'], + title: t('use_dates'), + width: '12rem', + render: (_, { use_dates_start, use_dates_end, weekdays, audit_state_id, lastedit_changed }) => { + const _changed = parseJson(lastedit_changed); + const preValue = + colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.use_dates_start) || isNotEmpty(_changed.use_dates_end)) ? ( +
+ {isNotEmpty(_changed.use_dates_start) ? {_changed.use_dates_start} : use_dates_start} ~{' '} + {isNotEmpty(_changed.use_dates_end) ? {_changed.use_dates_end} : use_dates_end} +
+ ) : null; + const editCls = colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.use_dates_start) || isNotEmpty(_changed.use_dates_end)) ? 'text-danger' : ''; + return ( +
+ {preValue} + {`${use_dates_start} ~ ${use_dates_end}`} +
+ ); + }, + }, + { + key: 'weekdays', + dataIndex: ['weekdays'], + title: t('Weekdays'), + width: '6rem', + render: (text, { weekdays, audit_state_id, lastedit_changed }) => { + const _changed = parseJson(lastedit_changed); + const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id); + const ifData = !isEmpty((_changed.weekdayList || []).filter((s) => s)); + const preValue = ifCompare && ifData ?
{_changed.weekdayList}
: null; + const editCls = ifCompare && ifData ? 'text-danger' : ''; + return ( +
+ {preValue} + {text || t('Unlimited')} +
+ ); + }, + }, +]; + +const useLogMethod = (method) => { + const { t } = useTranslation('products'); + const methodMap = { + 'history': { + title: '📑' + t('versionHistory'), + btnText: t('versionHistory'), + fetchData: async (params) => { + const data = await getPPLogAction(params); + return {data}; + }, + }, + 'published': { + title: '✅' + t('versionPublished'), + btnText: t('versionPublished'), + fetchData: async (params) => { + const { travel_agency_id, product_id, price_id, use_year } = params; + const data = await getPPRunningAction({ travel_agency_id, product_id_list: product_id, use_year }); + return {data: data?.[0]?.quotation || []}; + }, + }, + 'snapshot': { + title: '📷' + t('versionSnapshot'), + btnText: t('versionSnapshot'), + subTitle: t('点击左侧价格版本查看具体价格'), + fetchData: async (params) => { + const { price_id, ..._params } = params; + const data = await getPPSnapshotAction(_params); + return {data}; //?.[0]?.quotation || []; + }, + }, + }; + return methodMap[method]; +}; + +/** + * ProductQuotationLogPopover - A popover component that displays product quotation change logs or published data + * + * This component shows a history of price changes for a specific product quotation in a popover table. + * It supports displaying different data sources (history logs or published data) and shows + * comparison between previous and current values with visual indicators. + * + * @param {Object} props - Component props + * @param {string} props.btnText - The text to display on the trigger button and in the popover header + * @param {'history' | 'published' | 'snapshot'} props.method - Determines data source - "history" for change logs or "published" for published quotations + * @param {Object} props.triggerProps - Additional props to pass to the trigger button + * @param {number} props.travel_agency_id - ID of the travel agency (used in data fetching) + * @param {number} props.product_id - ID of the product (used in data fetching) + * @param {number} props.price_id - ID of the price entry (used in data fetching) + * @param {number} props.use_year - Year to use for fetching data (used in data fetching) + * @param {Function} props.onOpenChange - Callback function to be called when the popover opens or closes + */ +const ProductQuotationSnapshotPopover = ({ method, triggerProps = {}, onOpenChange, ...props }) => { + const { travel_agency_id, product_id, price_id, use_year } = props; + + const { t } = useTranslation('products'); + const [open, setOpen] = useState(false); + const [logData, setLogData] = useState([]); + + const { title, subTitle, btnText: methodBtnText, fetchData } = useLogMethod(method); + + const tablePagination = useMemo(() => method === 'history' ? { pageSize: 5, position: ['bottomLeft']} : { pageSize: 10, position: ['bottomLeft']}, [method]); + + const [viewSnapshotItem, setViewSnapshotItem] = useState([]); + const [loading, setLoading] = useState(false); + const getData = async () => { + setLoading(true); + const { data } = await fetchData({ travel_agency_id, product_id, price_id, use_year }); + setLogData(data); + invokeOpenChange(true); + setLoading(false); + }; + + const invokeOpenChange = (_open) => { + if (typeof onOpenChange === 'function') { + onOpenChange(_open); + } + }; + + const onClickSnapshotItem = (item) => { + console.log(item) + setViewSnapshotItem(item); + console.log('cc\n'); + const chunk = chunkBy(2025, [{...item, quotation: item.quotation.map(q => ({...q, WPI_SN: product_id })), info: { id: product_id }}], ['quote_season', 'quote_size']); + console.log(chunk) + }; + + const columns = [...columnsSets(t, false), { title: '时间', dataIndex: 'updatetime', key: 'updatetime' }]; + return ( + + {title} + {subTitle && {subTitle}} + +
+ } + content={ + <> + + ( + onClickSnapshotItem(item)} className={viewSnapshotItem.version === item.version ? 'active' : ''}> + {item.version} + + )} + pagination={{ pageSize: 5, size: 'small', showLessItems: true, simple: { readOnly: true } }} + className=' cursor-pointer basis-48 flex flex-col [&>*:first-child]:flex-1 [&_.ant-list-pagination]:m-1 [&_.ant-list-item]:py-1 [&_.ant-list-item.active]:bg-blue-100' + /> +
+ + + + + } + trigger={['click']} + open={open} + onOpenChange={(v) => { + setOpen(v); + invokeOpenChange(v); + if (v === false) { + setLogData([]); + setViewSnapshotItem([]); + } + }}> + + + ); +}; +export default ProductQuotationSnapshotPopover; diff --git a/src/views/products/PickYear.jsx b/src/views/products/PickYear.jsx index 28c1eba..399ec16 100644 --- a/src/views/products/PickYear.jsx +++ b/src/views/products/PickYear.jsx @@ -21,15 +21,10 @@ function PickYear() { variant="underlined" needConfirm inputReadOnly={true} - minDate={dayjs().add(-30, "year")} + minDate={dayjs('2022-01-01')} maxDate={dayjs().add(2, "year")} allowClear={false} picker="year" - styles={{ - root: { - color: 'red' - } - }} open={true} onOk={(date) => { const useYear = date.year(); diff --git a/src/views/products/Print/AgencyContract.jsx b/src/views/products/Print/AgencyContract.jsx index aeeede0..3b40a07 100644 --- a/src/views/products/Print/AgencyContract.jsx +++ b/src/views/products/Print/AgencyContract.jsx @@ -1,4 +1,4 @@ -import { flush, groupBy, isEmpty, isNotEmpty, unique, uniqWith } from '@/utils/commons' +import { isEmpty } from '@/utils/commons' import dayjs from 'dayjs' import { AlignmentType, @@ -19,192 +19,12 @@ import { TextRun, WidthType, } from 'docx' +import { splitTable_6, splitTable_7, splitTable_B, splitTable_D, splitTable_J, splitTable_Q, splitTable_R, splitTable_8 } from '@/hooks/useProductsQuotationFormat'; +import { formatGroupSize } from '@/hooks/useProductsSets' -// Shoulder Season 平季; peak season 旺季 -const isFullYearOrLonger = (year, startDate, endDate) => { - // Parse the dates - const start = dayjs(startDate, 'YYYY-MM-DD') - const end = dayjs(endDate, 'YYYY-MM-DD') - - // Create the start and end dates for the year - const yearStart = dayjs(`${year}-01-01`, 'YYYY-MM-DD') - const yearEnd = dayjs(`${year}-12-31`, 'YYYY-MM-DD') - - // Check if start is '01-01' and end is '12-31' and the year matches - const isFullYear = start.isSame(yearStart, 'day') && end.isSame(yearEnd, 'day') - - // Check if the range is longer than a year - const isLongerThanYear = end.diff(start, 'year') >= 1 - - return isFullYear || isLongerThanYear -} - -const shortDate = (date) => dayjs(date).format('MM.DD') - -/** - * 车辆人等配置 - * 5座, 7座, 10座, 10以上 - */ -const chunkCarSets = (numbers) => { - return numbers.reduce( - (acc, number) => { - if (number < 5) { - acc.lt5.push(number) - } else if (number < 7) { - acc.lt7.push(number) - } else if (number < 10) { - acc.lt7.push(number) - } else { - acc.gt10.push(number) - } - return acc - }, - { lt5: [], lt7: [], lt10: [], gt10: [] }, - ) -} - -const uniqueBySub = (arr) => - arr.filter((subArr1, _, self) => { - return !self.some((subArr2) => { - if (subArr1 === subArr2) return false // don't compare a subarray with itself - const set1 = new Set(subArr1) - const set2 = new Set(subArr2) - // check if subArr1 is a subset of subArr2 - return [...set1].every((value) => set2.has(value)) - }) - }) - -const isFlatUnique = (bigArray) => { - let flattenedArray = bigArray.flat() - return flattenedArray.every((i) => { - let count = flattenedArray.filter((j) => j === i).length - return count === 1 - }) -} - -const chunkBy = (use_year, dataList = [], by = []) => { - const dataRollSS = dataList.map((rowp, ii) => { - const quotation = rowp.quotation.map((quoteItem) => { - return { - ...quoteItem, - quote_season: isFullYearOrLonger(use_year, quoteItem.use_dates_start, quoteItem.use_dates_end) ? 'SS' : 'PS', - } - }) - return { ...rowp, quotation } - }) - - // 人等分组只取平季, 因为产品只一行 - const allQuotesSS = dataRollSS.reduce( - (acc, rowp) => acc.concat(rowp.quotation.filter((q) => q.quote_season === 'SS')), - [], - ) - - const allQuotesPS = dataRollSS.reduce( - (acc, rowp) => acc.concat(rowp.quotation.filter((q) => q.quote_season === 'PS')), - [], - ) - const allQuotesSSS = isEmpty(allQuotesSS) ? allQuotesPS : allQuotesSS - - const PGroupSizeSS = allQuotesSSS.reduce((aq, cq) => { - aq[cq.WPI_SN] = aq[cq.WPI_SN] || [] - aq[cq.WPI_SN].push(cq.group_size_min) - aq[cq.WPI_SN] = unique(aq[cq.WPI_SN]) - aq[cq.WPI_SN] = aq[cq.WPI_SN].slice().sort((a,b) => a-b) - return aq - }, {}) - - const maxGroupSize = Math.max(...allQuotesSSS.map((q) => q.group_size_max)) - const maxSet = maxGroupSize === 1000 ? Infinity : maxGroupSize - - const _SSMinSet = uniqWith(Object.values(PGroupSizeSS), (a, b) => a.join(',') === b.join(',')) - // const uSSsizeSetArr = (_SSMinSet) - const uSSsizeSetArr = uniqueBySub(_SSMinSet) - - // * 若不重叠分组, 则上面不要 uniqueBySub - for (const key in PGroupSizeSS) { - if (Object.prototype.hasOwnProperty.call(PGroupSizeSS, key)) { - const element = PGroupSizeSS[key] - const findSet = uSSsizeSetArr.find((minCut) => element.every((v) => minCut.includes(v))) - PGroupSizeSS[key] = findSet - } - } - - const [SSsizeSets, PSsizeSets] = [uSSsizeSetArr, []].map((arr) => { - const _arr = structuredClone(arr) - const arrSets = _arr.map((keyMins) => - keyMins.reduce((acc, curr, idx, minsArr) => { - const _max = idx === minsArr.length - 1 ? maxSet : Number(minsArr[idx + 1]) - 1 - acc.push([Number(curr), _max]) - return acc - }, []), - ) - return arrSets - }) - - const compactSizeSets = { - SSsizeSetKey: uSSsizeSetArr.map((s) => s.join(',')).filter(isNotEmpty), - sizeSets: SSsizeSets, - } - - const chunkSS = structuredClone(dataRollSS).map((rowp) => { - const pkey = (PGroupSizeSS[rowp.info.id] || []).join(',') || compactSizeSets.SSsizeSetKey[0] // todo: - - const thisRange = (PGroupSizeSS[rowp.info.id] || []).reduce((acc, curr, idx, minsArr) => { - const _max = idx === minsArr.length - 1 ? maxSet : Number(minsArr[idx + 1]) - 1 - acc.push([Number(curr), _max]) - return acc - }, []) - const _quotation = rowp.quotation.map((quoteItem) => { - const ssSets = isEmpty(thisRange) ? SSsizeSets[0] : structuredClone(thisRange).reverse() - - const matchRange = ssSets.find((ss) => quoteItem.group_size_min >= ss[0] && quoteItem.group_size_max <= ss[1]) - const findEnd = - matchRange || - ssSets.find((ss) => quoteItem.group_size_max > ss[0] && quoteItem.group_size_max <= ss[1] && ss[1] !== Infinity) - const findStart = findEnd || ssSets.find((ss) => quoteItem.group_size_min >= ss[0]) - const finalRange = findStart || ssSets[0] - - quoteItem.quote_size = finalRange.join('-') - return quoteItem - }) - const quote_chunk_flat = groupBy(_quotation, (quoteItem2) => by.map((key) => quoteItem2[key]).join('@')) - const quote_chunk = Object.keys(quote_chunk_flat).reduce((qc, ckey) => { - const ckeyArr = ckey.split('@') - if (isEmpty(qc[ckeyArr[0]])) { - qc[ckeyArr[0]] = ckeyArr[1] ? { [ckeyArr[1]]: quote_chunk_flat[ckey] } : quote_chunk_flat[ckey] - } else { - qc[ckeyArr[0]][ckeyArr[1]] = (qc[ckeyArr[0]][ckeyArr[1]] || []).concat(quote_chunk_flat[ckey]) - } - return qc - }, {}) - return { - ...rowp, - sizeSetsSS: pkey, - quotation: _quotation, - quote_chunk, - } - }) - - const allquotation = chunkSS.reduce((a, c) => a.concat(c.quotation), []) - // 取出两季相应的时效区间 - const SSRange = unique( - (allquotation || []) - .filter((q) => q.quote_season === 'SS') - .map((qr) => `${qr.use_dates_start}~${qr.use_dates_end}`), - ) - const PSRange = unique( - (allquotation || []) - .filter((q) => q.quote_season === 'PS') - .map((qr) => `${qr.use_dates_start}~${qr.use_dates_end}`), - ) - - return { - chunk: chunkSS, - dataSource: chunkSS, - SSRange, - PSRange, - ...compactSizeSets, - } +const unitMap = { + '0': '人', + '1': '团', } const DOC_TITLE = '地接合同' @@ -227,11 +47,30 @@ const tableBorderOne = { left: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, right: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, } +const tableBorderInner = { + top: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, +} +const tableBorderInnerB = { + top: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, +} +const tableBorderInnerR = { + top: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, +} /** - * v1120 + * @version + * @date 2025-08-20 */ export default class AgencyContract { - #remarkList = [] + #remarkList = {} create([headerParams, activeAgency, agencyProducts, agencyExtras, remarks]) { this.#remarkList = remarks const { use_year } = headerParams @@ -449,7 +288,15 @@ export default class AgencyContract { this.createSubHeading(`餐费(元/人/餐)`, { numbering: { reference: 'products-type', level: 0 }, }), - ...this.createTable_R(use_year, agencyProducts['R'], agencyExtras), + ...this.createTable_R(use_year, agencyProducts['R']), + ]), + ...(isEmpty(agencyProducts['8']) + ? [] + : [ + this.createSubHeading(`附加项目(元/团,部分价格为元/人单独注明)`, { + numbering: { reference: 'products-type', level: 0 }, + }), + ...this.createTable_8(use_year, agencyProducts['8']), ]), ...(isEmpty(agencyProducts['D']) ? [] @@ -482,10 +329,12 @@ export default class AgencyContract { * 综费 */ createTable_6(use_year, dataList, style = {}) { - const { chunk, SSRange, sizeSets } = chunkBy(use_year, dataList, ['quote_season']) + const { tables, SSRange } = splitTable_6(use_year, dataList, false); + const defaultUnit = '0'; - //当前表格的备注信息 - const [remarkItem] = this.#remarkList.filter((i) => i.product_type_id == '6') + const table2Rows = tables.reduce((acc, {info, SS}) => { + return acc.concat(SS.map((v, i) => ({...v, info, rowSpan: i===0 ? SS.length : 0}))); + }, []); return new Table({ borders: tableBorderNone, @@ -498,37 +347,80 @@ export default class AgencyContract { children: [ this.createTableHeader('项目', 30), this.createTableHeader('人等', 20), - this.createTableHeader([...SSRange], 50), + this.createTableHeader([...SSRange], 50) ], }), - ...chunk.reduce((acc, row, ri) => { - return acc.concat(this.createDetailRowSizeSS(sizeSets, row, ri)) - }, []), + ...table2Rows.map( + (row, ri) => + new TableRow({ + children: [ + ...(ri === 0 + ? [ + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + rowSpan: row.rowSpan, + children: [ + new Paragraph({ + text: `${row?.info?.product_title}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + ] + : []), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: formatGroupSize(row.group_size_min, row.group_size_max, true), + alignment: AlignmentType.CENTER, + }), + ], + }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + // text: `${row.adult_cost ?? ''}`, + children: [ + new TextRun({ + text: `${row?.adult_cost ?? ''}`, + }), + ...(row?.adult_cost && row?.unit_id !== defaultUnit + ? [ + new TextRun({ + // text: `/${row?.unit_name || ''}`, + text: `/${unitMap[row?.unit_id] || row?.unit_name || ''}`, + // break: 1, + }), + ] + : []), + ], + alignment: AlignmentType.CENTER, + }), + ], + }), + ], + }) + ), - this.createTable_Memo(3, remarkItem.Memo.split(`\n`)), + this.createTable_Memo(3, this.#remarkList['6'].Memo.split(`\n`)), ], - }) + }); } /** * 超公里 */ createTable_B(use_year, dataList) { // console.log('*********************************************************************************************'); - const { chunk, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_size']) - //当前表格的备注信息 - const [remarkItem] = this.#remarkList.filter((i) => i.product_type_id == 'B') - const infoCol = (rowp) => - new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, - children: [ - new Paragraph({ - text: `${rowp?.info?.km}`, - alignment: AlignmentType.CENTER, - }), - ], - }) - const tableS = sizeSets.reduce((acc, size) => { + const { tables, SSRange, PSRange } = splitTable_B(use_year, dataList, false); + const defaultUnit = '0'; + const showRangeCol = PSRange.length > 0; + + const tableS = tables.reduce((acc, { cols, data }) => { const subTable = new Table({ borders: tableBorderNone, width: { @@ -539,9 +431,10 @@ export default class AgencyContract { new TableRow({ children: [ this.createTableHeader('超公里项目', 20), - this.createTableHeader('往返公里数', 20), - this.createTableHeader([...SSRange], 60, { - columnSpan: size.length, + this.createTableHeader('往返公里数', 15), + ...(showRangeCol ? [this.createTableHeader('', 15)] : []), + this.createTableHeader([...SSRange], 65, { + columnSpan: cols.length, }), ], }), @@ -557,40 +450,112 @@ export default class AgencyContract { verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })], }), - ...size.map( + ...(showRangeCol + ? [ + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: '时段', alignment: 'center' })], + }), + ] + : []), + ...cols.map( (ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [ new Paragraph({ - text: ss[1] === Infinity ? `${ss[0]}人以上` : `${unique(ss.filter((x) => x)).join('-')}人`, + text: formatGroupSize(...ss, true), alignment: AlignmentType.CENTER, }), ], - }), + }) ), ], }), - ...chunk - .filter((p) => p.sizeSetsSS === size.map((mr) => mr[0]).join(',')) - .reduce( - (acc, row, ri) => - acc.concat( - this.createDetailRowSize(size, row, ri, { - infoCol, - withSize: false, - defaultUnit: '0', - }), - ), - [], - ), + ...data.map( + (row, ri) => + new TableRow({ + children: [ + ...(row.rowSpan > 0 + ? row.info?.isCityRow + ? [ + this.createTableHeader(row.info?.product_title || '', 20), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: '' })], + }), + ] + : [ + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: row.info?.product_title || '' })], + rowSpan: row.rowSpan, + }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: `${row.info?.km ?? ''}`, alignment: AlignmentType.CENTER })], + rowSpan: row.rowSpan, + }), + ] + : []), + ...(showRangeCol + ? [ + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + ...(row.quote_season === 'SS' + ? row.rowSpan > 1 + ? ['平季', '(除特殊时段外)'].map((str) => new Paragraph({ text: str, alignment: AlignmentType.LEFT })) + : [new Paragraph({ text: '' })] + : ['特殊时段', `${row.use_dates_start.replace(`${use_year}.`, '')}~${row.use_dates_end.replace(`${use_year}.`, '')}`].map( + (str) => new Paragraph({ text: str, alignment: AlignmentType.LEFT }) + )), + ], + }), + ] + : []), + ...cols.map( + (ss) => + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + // text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + children: [ + new TextRun({ + text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + }), + ...(row?.[formatGroupSize(...ss)]?.adult_cost && row?.[formatGroupSize(...ss)]?.unit_id !== defaultUnit + ? [ + new TextRun({ + // text: `/${row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + text: `/${unitMap[row?.[formatGroupSize(...ss)]?.unit_id] || row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + break: 1, + }), + ] + : []), + ], + alignment: AlignmentType.CENTER, + }), + ], + }) + ), + ], + }) + ), - this.createTable_Memo(2 + size.length, remarkItem.Memo.split(`\n`)), + this.createTable_Memo(2 + cols.length + (showRangeCol ? 1 : 0), this.#remarkList['B'].Memo.split(`\n`)), ], - }) - acc.push(subTable) - acc.push(new Paragraph({ text: `` })) + }); + acc.push(subTable); + acc.push(new Paragraph({ text: `` })) // 多表格之间分隔一行 return acc }, []) return tableS @@ -600,16 +565,11 @@ export default class AgencyContract { * 车费 */ createTable_J(use_year, dataList) { - const chunk_By = ['quote_size'] - const showPSCol = false + const { tables, SSRange, PSRange, } = splitTable_J(use_year, dataList, false); const defaultUnit = '1' - const { dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, chunk_By) const showRangeCol = PSRange.length > 0; - //当前表格的备注信息 - const [remarkItem] = this.#remarkList.filter((i) => i.product_type_id == 'J') - const tableHeader = this.createTableHeader - const tableS = sizeSets.reduce((rt, setItem) => { + const tableS = tables.reduce((acc, { cols, data }) => { const subTable = new Table({ borders: tableBorderNone, width: { @@ -619,14 +579,9 @@ export default class AgencyContract { rows: [ new TableRow({ children: [ - tableHeader('项目', 15), - ...(showRangeCol ? [tableHeader('', 15)] : []), - tableHeader([...SSRange], 30, { columnSpan: setItem.length + 1 }), - showPSCol - ? tableHeader(['旺季', ...PSRange], 40, { - columnSpan: setItem.length, - }) - : undefined, + this.createTableHeader('项目', 20), + ...(showRangeCol ? [this.createTableHeader('', 15)] : []), + this.createTableHeader([...SSRange], 65, { columnSpan: cols.length }), ], }), //人数区间行 @@ -642,58 +597,92 @@ export default class AgencyContract { verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '时段', alignment: 'center' })], })]: []), - ...setItem.map((ss) => { + ...cols.map((ss) => { return new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, width: { size: 2000, type: WidthType.DXA }, children: [ new Paragraph({ - text: ss[1] === Infinity ? `${ss[0]}人以上` : `${unique(ss.filter((x) => x)).join('-')}人`, + text: formatGroupSize(...ss, true), alignment: AlignmentType.CENTER, }), ], }); }), - ...(showPSCol - ? setItem.map( + ], + }), + //动态生成数据行 + ...data.map( + (row, ri) => + new TableRow({ + children: [ + ...(row.rowSpan > 0 + ? [ + row.info?.isCityRow + ? this.createTableHeader(row.info?.product_title || '', 20) + : new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: row.info?.product_title || '' })], + rowSpan: row.rowSpan, + }), + ] + : []), + ...(showRangeCol + ? [ + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + ...(row.quote_season === 'SS' + ? row.rowSpan > 1 + ? ['平季', '(除特殊时段外)'].map((str) => new Paragraph({ text: str, alignment: AlignmentType.LEFT })) + : [new Paragraph({ text: '' })] + : ['特殊时段', `${row.use_dates_start.replace(`${use_year}.`, '')}~${row.use_dates_end.replace(`${use_year}.`, '')}`].map( + (str) => new Paragraph({ text: str, alignment: AlignmentType.LEFT }) + )), + ], + }), + ] + : []), + ...cols.map( (ss) => new TableCell({ borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, verticalAlign: AlignmentType.CENTER, - columnSpan: sizeSets.length + 1, children: [ new Paragraph({ - text: ss[1] === Infinity ? `${ss[0]}人以上` : `${unique(ss.filter((x) => x)).join('-')}人`, + // text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + children: [ + new TextRun({ + text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + }), + ...(row?.[formatGroupSize(...ss)]?.adult_cost && row?.[formatGroupSize(...ss)]?.unit_id !== defaultUnit + ? [ + new TextRun({ + // text: `/${row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + text: `/${unitMap[row?.[formatGroupSize(...ss)]?.unit_id] || row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + break: 1, + }), + ] + : []), + ], alignment: AlignmentType.CENTER, }), ], }) - ) - : []), - ], - }), - //动态生成数据行 - ...dataSource - .filter((p) => p.sizeSetsSS === setItem.map((mr) => mr[0]).join(',')) - .reduce((acc, row, ri) => { - return acc.concat( - this.createDetailRowSize_J(setItem, row, ri, { - withSize: false, - defaultUnit, - withSeason: showPSCol, - showRangeCol, - }) - ); - }, []), + ), + ], + }) + ), //备注行 - this.createTable_Memo(1 + setItem.length * 2, remarkItem.Memo.split(`\n`)), + this.createTable_Memo(1 + cols.length + (showRangeCol ? 1 : 0), this.#remarkList['J'].Memo.split(`\n`)), ], }); - rt.push(subTable) - rt.push(new Paragraph({ text: `` })) - return rt + acc.push(subTable) + acc.push(new Paragraph({ text: `` })) + return acc }, []) return tableS } @@ -702,17 +691,14 @@ export default class AgencyContract { * 包价线路 */ createTable_D(use_year, dataList) { - const chunk_By = ['quote_size'] - const showPSCol = false + const { tables, SSRange, PSRange } = splitTable_D(use_year, dataList, false); + + // console.log('DDDD', tables, SSRange, PSRange); const defaultUnit = '1' - const { dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, chunk_By) - const showRangeCol = PSRange.length > 0; + const showRangeCol = PSRange.length > 0; // 显示时段列 // console.log(sizeSets, dataSource) - //当前表格的备注信息 - const [remarkItem] = this.#remarkList.filter((i) => i.product_type_id == 'D') - const tableHeader = this.createTableHeader - const tableS = sizeSets.reduce((rt, setItem) => { + const tableS = tables.reduce((acc, { cols, data }) => { const subTable = new Table({ borders: tableBorderNone, width: { @@ -722,14 +708,9 @@ export default class AgencyContract { rows: [ new TableRow({ children: [ - tableHeader('项目', 20), - ...(showRangeCol ? [tableHeader('', 15)] : []), - tableHeader([...SSRange], 40, { columnSpan: setItem.length }), - showPSCol - ? tableHeader(['旺季', ...PSRange], 40, { - columnSpan: setItem.length, - }) - : undefined, + this.createTableHeader('项目', 20), + ...(showRangeCol ? [this.createTableHeader('', 15)] : []), + this.createTableHeader([...SSRange], 65, { columnSpan: cols.length }), ], }), new TableRow({ @@ -739,64 +720,108 @@ export default class AgencyContract { verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })], }), - ...(showRangeCol ? [new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, - children: [new Paragraph({ text: '时段', alignment: 'center' })], - })]: []), - ...setItem.map( + ...(showRangeCol + ? [ + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: '时段', alignment: 'center' })], + }), + ] + : []), + ...cols.map( (ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [ new Paragraph({ - // text: `${ss[0]}`, - text: ss[1] === Infinity ? `${ss[0]}人以上` : `${unique(ss.filter((x) => x)).join('-')}人`, + text: formatGroupSize(...ss, true), alignment: AlignmentType.CENTER, }), ], - }), + }) ), - ...(showPSCol - ? setItem.map( + ], + }), + ...data.map( + (row, ri) => + new TableRow({ + children: [ + ...(row.rowSpan > 0 + ? [ + row.info?.isCityRow + ? this.createTableHeader(row.info?.product_title || '', 20) + : new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: row.info?.product_title || '' })], + rowSpan: row.rowSpan, + }), + ] + : []), + ...(showRangeCol + ? [ + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + ...(row.quote_season === 'SS' + ? row.rowSpan > 1 + ? ['平季', '(除特殊时段外)'].map((str) => new Paragraph({ text: str, alignment: AlignmentType.LEFT })) + : [new Paragraph({ text: '' })] + : ['特殊时段', `${row.use_dates_start.replace(`${use_year}.`, '')}~${row.use_dates_end.replace(`${use_year}.`, '')}`].map( + (str) => new Paragraph({ text: str, alignment: AlignmentType.LEFT }) + )), + ], + }), + ] + : []), + ...cols.map( (ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [ new Paragraph({ - // text: `${ss[0]}`, - text: ss[1] === Infinity ? `${ss[0]}人以上` : `${unique(ss.filter((x) => x)).join('-')}人`, + // text: row?.[formatGroupSize(...ss)]?.adult_cost + // ? `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}` + + // (row.info?.isCityRow !== true && // && text + // row?.[formatGroupSize(...ss)]?.unit_id !== defaultUnit + // ? // ? ` /${row?.[formatGroupSize(...ss)]?.unit_name || ''}` + // ` /${unitMap[row?.[formatGroupSize(...ss)]?.unit_id] || ''}` + // : '') + // : '', + children: [ + new TextRun({ + text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + }), + ...(row?.[formatGroupSize(...ss)]?.adult_cost && row?.[formatGroupSize(...ss)]?.unit_id !== defaultUnit + ? [ + new TextRun({ + // text: `/${row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + text: `/${unitMap[row?.[formatGroupSize(...ss)]?.unit_id] || row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + break: 1, + }), + ] + : []), + ], alignment: AlignmentType.CENTER, }), ], - }), - ) - : []), - ], - }), - ...dataSource - .filter((p) => p.sizeSetsSS === setItem.map((mr) => mr[0]).join(',')) - .reduce( - (acc, row, ri) => - acc.concat( - this.createDetailRowSize_J(setItem, row, ri, { - withSize: false, - defaultUnit, - withSeason: showPSCol, - showRangeCol, - }), - ), - [], - ), + }) + ), + ], + }) + ), + //备注行 - this.createTable_Memo(1 + setItem.length * 2, remarkItem.Memo.split(`\n`)), + this.createTable_Memo(1 + cols.length + (showRangeCol ? 1 : 0), this.#remarkList['D'].Memo.split(`\n`)), ], - }) - rt.push(subTable) - rt.push(new Paragraph({ text: `` })) - return rt + }); + acc.push(subTable) + acc.push(new Paragraph({ text: `` })) + return acc }, []) return tableS } @@ -804,9 +829,9 @@ export default class AgencyContract { * 导游 */ createTable_Q(use_year, dataList) { - const { chunk, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season']) - //当前表格的备注信息 - const [remarkItem] = this.#remarkList.filter((i) => i.product_type_id == 'Q') + const tables = splitTable_Q(use_year, dataList, false); + const defaultUnit = '1' + // console.log('QQQ', tables); return new Table({ borders: tableBorderNone, @@ -817,87 +842,164 @@ export default class AgencyContract { rows: [ new TableRow({ children: [ - this.createTableHeader('项目', 30), - this.createTableHeader(['平季(除去旺季以外)'], 35), - this.createTableHeader(['旺季'], 35), + this.createTableHeader('项目', 40), + this.createTableHeader(['平季(除去旺季以外)'], 30), + this.createTableHeader(['旺季'], 30) ], }), - ...chunk - .map((row, ri) => this.createDetailRowSeason2(row, ri, { withSize: false })) - .reduce((a, c) => a.concat(c), []), + ...tables.map( + (row, ri) => + new TableRow({ + children: [ + row.info?.isCityRow + ? this.createTableHeader(row.info?.product_title || '', 40) + : new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: row.info?.product_title || '' })], + }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: + (row?.SS || []).length <= 1 + ? [new Paragraph({ + // text: `${row?.SS?.[0]?.adult_cost ?? ''}`, + children: [ + new TextRun({ + text: `${row?.SS?.[0]?.adult_cost ?? ''}`, + }), + ...(row?.SS?.[0].adult_cost && row?.SS?.[0]?.unit_id !== defaultUnit + ? [ + new TextRun({ + // text: `/${row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + text: `/${unitMap[row?.SS?.[0]?.unit_id] || row?.SS?.[0]?.unit_name || ''}`, + // break: 1, + }), + ] + : []), + ], + alignment: AlignmentType.CENTER + })] + : [ + new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: (row?.SS || []).map( + (quoteItem, ii, _arr) => + new TableRow({ + borders: tableBorderInner, + children: [ + new TableCell({ + borders: ii !== _arr.length - 1 ? tableBorderInner : tableBorderInnerR, + width: { size: 1200, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${formatGroupSize(quoteItem.group_size_min, quoteItem.group_size_max, true)}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + new TableCell({ + borders: ii !== _arr.length - 1 ? tableBorderInner : tableBorderInnerR, + width: { size: 1200, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${quoteItem.unit_name}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + new TableCell({ + borders: ii !== _arr.length - 1 ? tableBorderInnerB : tableBorderNone, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${quoteItem.adult_cost}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + ], + }) + ), + }), + ], + }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: + (row?.PS || []).length === 0 + ? [new Paragraph({ text: '', alignment: AlignmentType.CENTER })] + : [ + new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: (row?.PS || []).map( + (quoteItem, ii, _arr) => + new TableRow({ + borders: tableBorderInner, + children: [ + new TableCell({ + borders: ii !== _arr.length - 1 ? tableBorderInner : tableBorderInnerR, + // width: { size: 1800, type: WidthType.DXA }, + width: { size: 60, type: WidthType.PERCENTAGE }, + verticalAlign: AlignmentType.CENTER, + children: quoteItem.rows.map(d => new Paragraph({ alignment: AlignmentType.LEFT, text: `${d.use_dates_start.replace(`${use_year}.`, '')}~${d.use_dates_end.replace(`${use_year}.`, '')}` })), + }), + new TableCell({ + borders: ii !== _arr.length - 1 ? tableBorderInnerB : tableBorderNone, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + // text: `${quoteItem.adult_cost}`, + children: [ + new TextRun({ + text: `${quoteItem.adult_cost ?? ''}`, + }), + ...(quoteItem.adult_cost && quoteItem?.unit_id !== defaultUnit + ? [ + new TextRun({ + // text: `/${row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + text: `/${unitMap[quoteItem?.unit_id] || quoteItem?.unit_name || ''}`, + // break: 1, + }), + ] + : []), + ], + alignment: AlignmentType.CENTER, + }), + ], + }), + ], + }) + ), + }), + ], + }), + ], + }) + ), - this.createTable_Memo(3, remarkItem.Memo.split(`\n`)), + this.createTable_Memo(3, this.#remarkList['Q'].Memo.split(`\n`)), ], - }) + }); } /** * 景点 */ createTable_7(use_year, dataList, agencyExtras) { - const withExtras = dataList.map((row) => ({ - ...row, - extras: agencyExtras[row.info.id] || [], - })) - const { chunk, sizeSets, SSRange, PSRange } = chunkBy(use_year, withExtras, ['quote_season']) - //当前表格的备注信息 - const [remarkItem] = this.#remarkList.filter((i) => i.product_type_id == '7') - - const extraCol = (rowp, i) => { - return new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, - children: [ - ...(rowp.extras || []).reduce((ac, extra) => { - const allSize = unique(extra.quotation.map((eq) => `${eq.group_size_min}-${eq.group_size_max}`)) - const allDates = unique(extra.quotation.map((eq) => `${eq.use_dates_start}-${eq.use_dates_end}`)) - // const compactBy = allSize.length > 1 ? 'size' : allDates.length > 1 ? 'dates' : 'all'; - const compactBy = allSize.length < allDates.length ? 'size' : 'dates' - const compactQuotation = groupBy( - extra.quotation, - (ExtraQuoteItem) => - `${ExtraQuoteItem.adult_cost}@${ExtraQuoteItem.unit_id}.` + - (compactBy === 'size' - ? `[${ExtraQuoteItem.group_size_min}.${ExtraQuoteItem.group_size_max}]` - : `${ExtraQuoteItem.use_dates_start},${ExtraQuoteItem.use_dates_end}`), - ) - - const thisE = new Paragraph({ - children: [ - new TextRun({ - text: `${extra.info.product_title}-${extra.info.product_type_name}`, - bold: true, - }), - ], - }) - - // const thisP = Object.entries(compactQuotation).map(([key, [firstQ, ...others]]) => { - // const date1 = shortDate(firstQ.use_dates_start) - // const date2 = shortDate(firstQ.use_dates_end) - // const othersDates = others - // .map((oq) => `${shortDate(oq.use_dates_start)}~${shortDate(oq.use_dates_end)}`) - // .join(', ') - // const otherStr = isEmpty(othersDates) ? '' : `, ${othersDates}` - // const sizeStr = - // [0, 1].includes(firstQ.group_size_min) && firstQ.group_size_max === 1000 - // ? '不分人等,' - // : `${firstQ.group_size_min}-${firstQ.group_size_max}人,` - // return new Paragraph({ - // children: [ - // new TextRun({ - // text: - // `${sizeStr} ${firstQ.unit_name}, ${firstQ.adult_cost}` + - // (compactBy !== 'dates' ? `, ${date1}~${date2}${otherStr}` : ''), - // }), - // ], - // }) - // }) - - // return ac.concat([thisE, ...thisP]) - return ac.concat([thisE]) - }, []), - ], - }) - } + const tables = splitTable_7(use_year, dataList, false); + const defaultUnit = '0'; return new Table({ borders: tableBorderNone, @@ -914,62 +1016,189 @@ export default class AgencyContract { this.createTableHeader('附加费用', 25), ], }), - ...chunk.reduce( - (acc, row, ri) => - acc.concat( - this.createDetailRowSeason2(row, ri, { - withSize: true, - defaultUnit: '0', - extraCol, - }), - ), - [], + ...tables.map( + (row, ri) => + new TableRow({ + children: [ + row.info?.isCityRow + ? this.createTableHeader(row.info?.product_title || '', 25) + : new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: row.info?.product_title || '' })], + }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: + (row?.SS || []).length === 0 + ? [new Paragraph({ text: '', alignment: AlignmentType.CENTER })] + : (row?.SS || []).length === 1 && row?.SS?.[0]?.unit_id === defaultUnit + ? [new Paragraph({ text: `${row?.SS?.[0]?.adult_cost ?? ''}`, alignment: AlignmentType.CENTER })] + : [ + new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: (row?.SS || []).map( + (quoteItem, ii, _arr) => + new TableRow({ + borders: tableBorderInner, + children: [ + new TableCell({ + borders: ii !== _arr.length - 1 ? tableBorderInner : tableBorderInnerR, + width: { size: 1600, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${formatGroupSize(quoteItem.group_size_min, quoteItem.group_size_max, true)}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + new TableCell({ + borders: ii !== _arr.length - 1 ? tableBorderInner : tableBorderInnerR, + width: { size: 1200, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${quoteItem.unit_name}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + new TableCell({ + borders: ii !== _arr.length - 1 ? tableBorderInnerB : tableBorderNone, + width: { size: 1200, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${quoteItem.adult_cost}`, + alignment: AlignmentType.CENTER, + }), + ], + }), + ], + }) + ), + }), + ], + }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: + (row?.PS || []).length === 0 + ? [new Paragraph({ text: '', alignment: AlignmentType.CENTER })] + : [ + new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: (row?.PSData || []).reduce((accr, quoteItem0, ti, arrt) => { + const _header = new TableRow({ + borders: tableBorderInnerB, + children: [ + new TableCell({ + borders: tableBorderInnerB, + width: { size: 2000, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + columnSpan: 3, + children: quoteItem0.headerDates + .map((d) => `${d.use_dates_start.replace(`${use_year}.`, '')}~${d.use_dates_end.replace(`${use_year}.`, '')}`) + .map( + (text) => + new Paragraph({ + text: text, + alignment: AlignmentType.CENTER, + }) + ), + }), + ], + }); + const _rows = quoteItem0.rows.map( + (quoteItem, ii, _arr) => + new TableRow({ + borders: ti !== arrt.length - 1 ? tableBorderInnerB : tableBorderNone, + children: [ + new TableCell({ + borders: ti !== arrt.length - 1 ? tableBorderInner : tableBorderInnerR, + width: { size: 2000, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${formatGroupSize(quoteItem.group_size_min, quoteItem.group_size_max, true)}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + new TableCell({ + borders: ti !== arrt.length - 1 ? tableBorderInner : tableBorderInnerR, + width: { size: 1000, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${quoteItem.unit_name}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + new TableCell({ + borders: ti !== arrt.length - 1 ? tableBorderInnerB : tableBorderNone, + width: { size: 2400, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${quoteItem.adult_cost}`, + alignment: AlignmentType.CENTER, + }), + ], + }), + ], + }) + ); + return accr.concat([_header, ..._rows]); + }, []), + }), + ], + }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.LEFT, + children: (agencyExtras[row.info?.id] || []).map( + (extra) => + new Paragraph({ + children: [ + new TextRun({ + text: `${extra.info.product_title}-${extra.info.product_type_name_txt}`, + bold: true, + }), + ], + }) + ), + }), + ], + }) ), + //备注行 - this.createTable_Memo(1 + sizeSets.length * 2 + 1, remarkItem.Memo.split(`\n`)), + this.createTable_Memo(1 + 2 + 1, this.#remarkList['7'].Memo.split(`\n`)), ], - }) + }); } /** * 餐费 */ - createTable_R(use_year, dataList, agencyExtras) { - const withExtras = dataList.map((row) => ({ - ...row, - extras: agencyExtras[row.info.id] || [], - })) - const { chunk, sizeSets, SSRange, PSRange } = chunkBy(use_year, withExtras, ['quote_size']) - //当前表格的备注信息 - const [remarkItem] = this.#remarkList.filter((i) => i.product_type_id == 'R') - const extraCol = (rowp, i) => - new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, - children: [ - ...(rowp.extras || []).map( - (extra) => - new Paragraph({ - text: `${extra.info.product_title} ${extra.quotation?.[0]?.adult_cost || ''} ${ - extra.quotation?.[0]?.unit_name || '' - }`, - }), - ), - // new Paragraph({ text: '' }) - ], - }) - - const tableS = sizeSets.reduce((acc, size) => { - const thisChunk = chunk.filter((p) => p.sizeSetsSS === size.map((mr) => mr[0]).join(',')) - const currentChunkExtras = uniqWith(thisChunk.map((p) => p.extras).flat(), (a, b) => a.info.id === b.info.id) - const mergedUniqueExtra = currentChunkExtras.map( - (extra) => - new Paragraph({ - text: `${extra.info.product_title} ${extra.quotation?.[0]?.adult_cost || ''} ${ - extra.quotation?.[0]?.unit_name || '' - }`, - }), - ) + createTable_R(use_year, dataList) { + const { tables, SSRange, PSRange } = splitTable_R(use_year, dataList, false); + const defaultUnit = '0'; + // console.log('RRRRRR', tables); + const tableS = tables.reduce((acc, { cols, data }) => { const subTable = new Table({ borders: tableBorderNone, width: { @@ -981,7 +1210,7 @@ export default class AgencyContract { children: [ this.createTableHeader('社会餐厅午餐/晚餐', 25), this.createTableHeader([...SSRange, ...PSRange], 50, { - columnSpan: size.length, + columnSpan: cols.length, }), this.createTableHeader('司陪餐补(元/团/餐)', 25), ], @@ -993,14 +1222,14 @@ export default class AgencyContract { verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })], }), - ...size.map( + ...cols.map( (ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [ new Paragraph({ - text: ss[1] === Infinity ? `${ss[0]}人以上` : `${unique(ss.filter((x) => x)).join('-')}人`, + text: formatGroupSize(...ss, true), alignment: AlignmentType.CENTER, }), ], @@ -1008,7 +1237,7 @@ export default class AgencyContract { ), new TableCell({ borders: tableBorderOne, - rowSpan: thisChunk.length + 1, + rowSpan: data.length + 1, children: [ new Paragraph({ alignment: AlignmentType.CENTER, @@ -1018,485 +1247,179 @@ export default class AgencyContract { }), ], }), - ...thisChunk.reduce( - (acc, row, ri) => acc.concat(this.createDetailRowSize(size, row, ri, { withSize: false })), - [], - ), - - this.createTable_Memo(1 + size.length + 1, remarkItem.Memo.split(`\n`)), - ], - }) - acc.push(subTable) - acc.push(new Paragraph({ text: `` })) - return acc - }, []) - return tableS - } - - createDetailRowSizeSS = ( - sizeSets, - rowp, - ii, - { withSeason = false, withSize = true, infoCol = () => {}, extraCol = () => {} } = {}, - ) => { - const thisSizeCol = rowp.sizeSetsSS - .split(',') - .map((min, i, _arr) => [Number(min), _arr[i + 1] ? Number(_arr[i + 1]) - 1 : Infinity]) - - const sizeLength = thisSizeCol.length - - const quote_chunk = (rowp.quote_chunk?.SS || []).reduce((acc, cur, i) => { - return { ...acc, [cur.quote_size]: cur.adult_cost } - }, {}) - - const data = thisSizeCol.map((sizeRow, ri) => { - return new TableRow({ - children: flush([ - ...(ri === 0 - ? [ - new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, - rowSpan: sizeLength, - children: [ - new Paragraph({ - text: `${rowp?.info?.product_title}`, - alignment: AlignmentType.LEFT, - }), - ], - }), - infoCol(rowp), - ] - : []), - new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, + ...data.map((row, ri) => new TableRow({ children: [ - new Paragraph({ - text: - sizeRow[1] === Infinity ? `${sizeRow[0]}人以上` : `${unique(sizeRow.filter((x) => x)).join('-')}人`, - alignment: AlignmentType.CENTER, - }), - ], - }), - new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, - children: [ - new Paragraph({ - text: `${quote_chunk?.[sizeRow.join('-')] || ''}`, - alignment: AlignmentType.CENTER, + row.info?.isCityRow ? this.createTableHeader(row.info?.product_title || '', 25) : new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: row.info?.product_title || '' })], }), - ], - }), - extraCol(rowp, ii), - ]), - }) - }) - return data - } - - createDetailRowSize_J( - sizeSets, - rowp, - ii, - { withSeason = false, withSize = true, showRangeCol=true, infoCol = () => {}, extraCol = () => {}, defaultUnit = '0' } = {}, - ) { - const sizeCol = sizeSets.map((ss) => ss.join('-')) - - const ranges = rowp['quotation'].map((i) => - Object.assign(i, { - range: ((quote_season) => { - const range = `${dayjs(i.use_dates_start).format('M-D').replace(/-/gi, '.')}-${dayjs(i.use_dates_end) - .format('M-D') - .replace(/-/gi, '.')}` - if (quote_season == 'SS') return `平季(除特殊时段以外)` - else return `特殊时段(${range})` - })(i.quote_season), - }), - ) - - //每个项目(产品)有多少个行(不同的时间段) - const product_rows = [...new Map(ranges.map((item) => [item.range, item])).values()] - - const rows = product_rows.reduce((acc, i) => { - const row = [] - sizeCol.forEach((j) => { - const target = rowp['quotation'] - .filter( - (item) => - item.use_dates_start == i.use_dates_start && - item.use_dates_end == i.use_dates_end && - item.quote_size == j, - ) - .pop() - if (target) row.push(target) - else row.push(``) - }) - - return Object.assign(acc, { [i.range]: row }) - }, {}) - - return [ - ...Object.entries(rows).map(([rangeStr, arr], index) => { - return new TableRow({ - children: flush([ - //合并相同项目的第一列 - ...(index == 0 - ? [ + ...cols.map( + (ss) => new TableCell({ - rowSpan: index == 0 ? Object.values(rows).length : 0, borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [ new Paragraph({ - text: index == 0 ? `${rowp?.info?.product_title}` : '', - alignment: AlignmentType.LEFT, + // text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + children: [ + new TextRun({ + text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + }), + ...(row?.[formatGroupSize(...ss)]?.adult_cost && row?.[formatGroupSize(...ss)]?.unit_id !== defaultUnit + ? [ + new TextRun({ + // text: `/${row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + text: `/${unitMap[row?.[formatGroupSize(...ss)]?.unit_id] || row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + break: 1, + }), + ] + : []), + ], + alignment: AlignmentType.CENTER, }), ], }), - ] - : []), - ...(showRangeCol ? [new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, - children: [ - new Paragraph({ - text: Object.values(rows).length > 1 ? rangeStr : '', - alignment: AlignmentType.CENTER, - }), - ], - })] : []), - infoCol(rowp), - ...arr.map((quoteItem) => { - return new TableCell({ - borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, - verticalAlign: AlignmentType.CENTER, - children: [ - new Paragraph({ - alignment: AlignmentType.CENTER, - text: isEmpty(quoteItem) - ? '' - : withSize && String(quoteItem.unit_id) !== defaultUnit - ? `${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}, ${quoteItem.adult_cost}` - : `${quoteItem.adult_cost}`, - }), - ], - }) - }), - ...(withSeason - ? sizeCol.map( - (ss) => - new TableCell({ - borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, - verticalAlign: AlignmentType.CENTER, - children: [ - ...(rowp.quote_chunk?.['PS']?.[ss] || []).map( - (quoteItem, ii) => - new Paragraph({ - alignment: AlignmentType.CENTER, - // text: `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}${quoteItem.unit_name}`, - text: - withSize && String(quoteItem.unit_id) !== defaultUnit - ? `${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}, ${quoteItem.adult_cost}` - : `${quoteItem.adult_cost}`, - }), - ), - ], - }), - ) - : []), - extraCol(rowp, ii), - ]), - }) - }), - ] - } - - createDetailRowSize_D( - sizeSets, - rowp, - ii, - { withSeason = false, withSize = true, infoCol = () => {}, extraCol = () => {}, defaultUnit = '0' } = {}, - ) { - const sizeCol = sizeSets.map((ss) => ss.join('-')) + ), + ], + })), - const ranges = rowp['quotation'].map((i) => - Object.assign(i, { - range: ((quote_season) => { - const range = `${dayjs(i.use_dates_start).format('M-D').replace(/-/gi, '.')}-${dayjs(i.use_dates_end) - .format('M-D') - .replace(/-/gi, '.')}` - if (quote_season == 'SS') return `平季(除特殊时段以外)` - else return `特殊时段(${range})` - })(i.quote_season), - }), - ) + this.createTable_Memo(1 + cols.length + 1, this.#remarkList['R'].Memo.split(`\n`)), + ], + }) + acc.push(subTable) + acc.push(new Paragraph({ text: `` })) + return acc + }, []) - //每个项目(产品)有多少个行(不同的时间段) - const product_rows = [...new Map(ranges.map((item) => [item.range, item])).values()] + return tableS + } - //默认时间 全年 - const firstDayOfYear = dayjs().startOf('year').format('YYYY-MM-DD') - const lastDayOfYear = dayjs().endOf('year').format('YYYY-MM-DD') + /** + * 附加项目 + */ + createTable_8(use_year, dataList) { + const { tables, SSRange, PSRange, } = splitTable_8(use_year, dataList, false); + const defaultUnit = '1' + const showRangeCol = PSRange.length > 0; - return [ - new TableRow({ - children: flush([ - new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, + const tableS = tables.reduce((acc, { cols, data }) => { + const subTable = new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ children: [ - new Paragraph({ - text: `${rowp?.info?.product_title}`, - alignment: AlignmentType.LEFT, - }), + this.createTableHeader('附加项目', 20), + ...(showRangeCol ? [this.createTableHeader('', 15)] : []), + this.createTableHeader([...SSRange], 65, { columnSpan: cols.length }), ], }), - infoCol(rowp), - ...sizeCol.map((ss) => { - const subItems = (withSeason ? rowp.quote_chunk?.['SS']?.[ss] : rowp.quote_chunk[ss]) || [] - return new TableCell({ - borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, - verticalAlign: AlignmentType.CENTER, - children: [ - ...subItems.reduce((acc, quoteItem, index) => { - const start = dayjs(quoteItem.use_dates_start).format('M-D').replace('-', '.') - const end = dayjs(quoteItem.use_dates_end).format('M-D').replace('-', '.') - const year = dayjs(quoteItem.use_dates_start).year() - return acc.concat( - ...[ - new Paragraph({ - alignment: AlignmentType.CENTER, - text: isEmpty(quoteItem) - ? '' - : withSize && String(quoteItem.unit_id) !== defaultUnit - ? `${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}, ${quoteItem.adult_cost}` - : `${quoteItem.adult_cost}`, - }), - ...( !isFullYearOrLonger(year, quoteItem.use_dates_start, quoteItem.use_dates_end) - ? [ - new Paragraph({ - alignment: AlignmentType.CENTER, - text: `(${start}-${end})`, - }), - ] - : []), - ], - ) - }, []), - ], - }) - }), - ...(withSeason - ? sizeCol.map( - (ss) => - new TableCell({ - borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, - verticalAlign: AlignmentType.CENTER, - children: [ - ...(rowp.quote_chunk?.['PS']?.[ss] || []).map( - (quoteItem, ii) => - new Paragraph({ - alignment: AlignmentType.CENTER, - text: - withSize && String(quoteItem.unit_id) !== defaultUnit - ? `${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}, ${quoteItem.adult_cost}` - : `${quoteItem.adult_cost}`, - }), - ), - ], - }), - ) - : []), - extraCol(rowp, ii), - ]), - }), - ] - } - - createDetailRowSize( - sizeSets, - rowp, - ii, - { withSeason = false, withSize = true, infoCol = () => {}, extraCol = () => {}, defaultUnit = '0' } = {}, - ) { - const sizeCol = sizeSets.map((ss) => ss.join('-')) - - return [ - new TableRow({ - children: flush([ - new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, + //人数区间行 + new TableRow({ children: [ - new Paragraph({ - text: `${rowp?.info?.product_title}`, - alignment: AlignmentType.LEFT, + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: '' })], }), - ], - }), - infoCol(rowp), - ...sizeCol.map((ss) => { - return new TableCell({ - borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, - verticalAlign: AlignmentType.CENTER, - children: [ - ...((withSeason ? rowp.quote_chunk?.['SS']?.[ss] : rowp.quote_chunk[ss]) || []).map( - (quoteItem, ii) => + ...(showRangeCol ? [new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: '时段', alignment: 'center' })], + })]: []), + ...cols.map((ss) => { + return new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + width: { size: 2000, type: WidthType.DXA }, + children: [ new Paragraph({ + text: formatGroupSize(...ss, true), alignment: AlignmentType.CENTER, - text: isEmpty(quoteItem) - ? '' - : withSize && String(quoteItem.unit_id) !== defaultUnit - ? `${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}, ${quoteItem.adult_cost}` - : `${quoteItem.adult_cost}`, }), - ), - ], - }) + ], + }); + }), + ], }), - ...(withSeason - ? sizeCol.map( - (ss) => - new TableCell({ - borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, - verticalAlign: AlignmentType.CENTER, - children: [ - ...(rowp.quote_chunk?.['PS']?.[ss] || []).map( - (quoteItem, ii) => + //动态生成数据行 + ...data.map( + (row, ri) => + new TableRow({ + children: [ + ...(row.rowSpan > 0 + ? [ + row.info?.isCityRow + ? this.createTableHeader(row.info?.product_title || '', 20) + : new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [new Paragraph({ text: row.info?.product_title || '' })], + rowSpan: row.rowSpan, + }), + ] + : []), + ...(showRangeCol + ? [ + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + ...(row.quote_season === 'SS' + ? row.rowSpan > 1 + ? ['平季', '(除特殊时段外)'].map((str) => new Paragraph({ text: str, alignment: AlignmentType.LEFT })) + : [new Paragraph({ text: '' })] + : ['特殊时段', `${row.use_dates_start.replace(`${use_year}.`, '')}~${row.use_dates_end.replace(`${use_year}.`, '')}`].map( + (str) => new Paragraph({ text: str, alignment: AlignmentType.LEFT }) + )), + ], + }), + ] + : []), + ...cols.map( + (ss) => + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ new Paragraph({ + // text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + children: [ + new TextRun({ + text: `${row?.[formatGroupSize(...ss)]?.adult_cost ?? ''}`, + }), + ...(row?.[formatGroupSize(...ss)]?.adult_cost && row?.[formatGroupSize(...ss)]?.unit_id !== defaultUnit + ? [ + new TextRun({ + // text: `/${row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + text: `/${unitMap[row?.[formatGroupSize(...ss)]?.unit_id] || row?.[formatGroupSize(...ss)]?.unit_name || ''}`, + break: 1, + }), + ] + : []), + ], alignment: AlignmentType.CENTER, - // text: `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}${quoteItem.unit_name}`, - text: - withSize && String(quoteItem.unit_id) !== defaultUnit - ? `${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}, ${quoteItem.adult_cost}` - : `${quoteItem.adult_cost}`, }), - ), - ], - }), - ) - : []), - extraCol(rowp, ii), - ]), - }), - ] - } - - createDetailRowSeason2(rowp, ii, { withSize = true, defaultUnit = '0', extraCol = () => {} } = {}) { - // console.log(rowp); - return [ - new TableRow({ - children: [ - //标题列 - new TableCell({ - borders: tableBorderOne, - verticalAlign: AlignmentType.CENTER, - children: [ - new Paragraph({ - text: `${rowp?.info?.product_title}`, - alignment: AlignmentType.LEFT, - }), - ], - }), - //SS - new TableCell({ - borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, - verticalAlign: AlignmentType.CENTER, - children: [ - ...(rowp.quote_chunk['SS'] || []).map( - (quoteItem, ii, _arr) => - new Paragraph({ - alignment: AlignmentType.CENTER, - text: - (withSize && (String(quoteItem.unit_id) !== defaultUnit || _arr.length > 1) - ? `${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name},` - : '') + `${quoteItem.adult_cost}`, - }), - ), - ], - }), - //PS - new TableCell({ - borders: tableBorderOne, - width: { size: 2000, type: WidthType.DXA }, - verticalAlign: AlignmentType.CENTER, - children: [ - //一个立即执行函数 - ...(() => { - //存储价格的数组 - const helper = [] - //PS 是所有旺季的数据 - const PS = rowp.quote_chunk['PS'] || [] - //第一次迭代获取所有价格和时间区间的数组 - const AllDatas = PS.reduce((acc, quoteItem, ii) => { - //价格文本 - const text = - (withSize && String(quoteItem.unit_id) === defaultUnit - ? `${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name},` - : '') + `${quoteItem.adult_cost}` - - //计算开始日期和结束日期 - const start = dayjs(quoteItem.use_dates_start).format('M-D').replace('-', '.') - const end = dayjs(quoteItem.use_dates_end).format('M-D').replace('-', '.') - //添加到数组的元素 - // const group = { ...quoteItem, text, range:start_year == end_year ? `${start}-${end}; ` : `${start_year}.${start}-${end_year}.${end}; ` } - const group = { ...quoteItem, text, range: `${start}-${end}; ` } - acc.push(group) - return acc - }, []) - - //第二次迭代对重复价格的元素进行时间区间字符串拼接 - const data = AllDatas.reduce((acc, obj) => { - const { text } = obj - //判断数组中是否已经有当前价格的元素 - if (acc.some((i) => i.text == text)) { - acc = acc.map((j) => { - //如果数组中已存在当前价格的元素,合并他们的时间区间字符串 - if (j.text == text) return { text, range: j.range + obj.range } - else return j - }) - } else acc.push(obj) - - return acc - }, []) - - for (let index = 0; index < data.length; index++) { - const element = data[index] - //添加价格 - helper.push( - new Paragraph({ - alignment: AlignmentType.CENTER, - text: element.text, - }), - ) - //添加时间 - helper.push( - new Paragraph({ - alignment: AlignmentType.CENTER, - text: `(${element.range})`, - }), - ) - } - - return helper - })(), - ], - }), - extraCol(rowp, ii), + ], + }) + ), + ], + }) + ), + //备注行 + this.createTable_Memo(1 + cols.length + (showRangeCol ? 1 : 0), (this.#remarkList?.['8']?.Memo || '').split(`\n`)), ], - }), - ] + }); + acc.push(subTable) + acc.push(new Paragraph({ text: `` })) + return acc + }, []) + return tableS } - createTable_Memo(columnSpan, remarkContent = []) { return new TableRow({ children: [ diff --git a/src/views/products/Print/AgencyPreview.jsx b/src/views/products/Print/AgencyPreview.jsx new file mode 100644 index 0000000..78d5771 --- /dev/null +++ b/src/views/products/Print/AgencyPreview.jsx @@ -0,0 +1,1198 @@ +import { useState } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { Button, Drawer, Card, Table } from 'antd'; +import { useTranslation } from 'react-i18next'; +import useProductsStore, { getAgencyAllExtrasAction } from '@/stores/Products/Index'; +import { useProductsTypes, formatGroupSize, useProductsTypesMapVal } from '@/hooks/useProductsSets'; +import { chunkBy, splitTable_6, splitTable_7, splitTable_8, splitTable_B, splitTable_D, splitTable_J, splitTable_Q, splitTable_R } from '@/hooks/useProductsQuotationFormat'; +import { groupBy, isNotEmpty } from '@/utils/commons'; +import useAuthStore from '@/stores/Auth'; +import { PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config'; +import ExportDocxBtn from './ExportDocxBtn'; + +const AgencyPreview = ({ params, ...props }) => { + const isPermitted = useAuthStore(state => state.isPermitted); + + const { t } = useTranslation(); + const { travel_agency_id, use_year } = params; + const productsTypes = useProductsTypes(); + + const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]); + const [setEditingProduct] = useProductsStore((state) => [state.setEditingProduct]); + + const productsTypesMapVal = useProductsTypesMapVal(); + + const [previewMode, setPreviewMode] = useState(false); + const [tables, setTables] = useState([]); + const [extras, setExtras] = useState([]); + + const handlePreview = async () => { + setPreviewMode(true); + + const agencyExtras = await getAgencyAllExtrasAction(params); + setExtras(agencyExtras); + // console.log(agencyExtras) + + // 只显示有产品的类型; 展开产品的价格表, 合并名称列; 转化为价格主表, 携带产品属性信息 + const hasDataTypes = Object.keys(agencyProducts); + const _show = productsTypes + .filter((kk) => hasDataTypes.includes(kk.value)) + .map((typeKey) => { + const typeProducts = agencyProducts[typeKey.value]; + const chunked = chunkBy(use_year, typeProducts); + // console.log(typeKey.label, chunked); + // return {...chunked, typeKey}; + const { SSRange, PSRange, SSsizeSetKey, SSsizeSetsMap, chunk } = chunked; + const bySizeSetKey = groupBy(chunk, 'sizeSetsSS'); // next: city + // return bySizeSetKey + const tables = Object.keys(bySizeSetKey).map((sizeSetsStr) => { + const _thisSSsetProducts = bySizeSetKey[sizeSetsStr]; + return { cols: SSsizeSetsMap[sizeSetsStr], colsKey: sizeSetsStr, data: _thisSSsetProducts }; + }); + return { tables, typeKey }; + }); + // console.log(_show); + setTables(_show); + + // const chunkR = chunkBy(use_year, agencyProducts['D'], []); // 'city_id', + // console.log(chunkR); + }; + + const cityRowHighlights = (record) => (record.info.isCityRow ? 'font-bold text-center ' : '') + + const renderTitle = (text, r) => { + const title = text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || ''; + const itemLink = isPermitted(PERM_PRODUCTS_OFFER_AUDIT) + ? `/products/${travel_agency_id}/${use_year}/all/edit` + : isPermitted(PERM_PRODUCTS_OFFER_PUT) + ? `/products/edit` + : ''; + return ( +
+ {isNotEmpty(itemLink) && (r.info?.isCityRow !== true) ? ( +
setEditingProduct({ info: r.info })}> + {title} +
+ ) : ( + title + )} + {r.info.id} +
+ ); + }; + + const renderTable_6 = () => { + // console.log('666666'); + if (!('6' in agencyProducts)) { + return null; + } + const {tables, SSRange} = splitTable_6(use_year, agencyProducts['6'], false); + const table2Rows = tables.reduce((acc, {info, SS}) => { + return acc.concat(SS.map((v, i) => ({...v, info, rowSpan: i===0 ? SS.length : 0}))); + }, []); + // console.log('tablesQuote', tablesQuote) + // console.log('table2Rows', table2Rows) + return ( + <> +
{ + return { rowSpan: record.rowSpan }; + }, + }, + { + title: '人等', + dataIndex: 'dateText', + key: 'dateText', + width: '5rem', + render: (_, r) =>
{formatGroupSize(r.group_size_min, r.group_size_max, true)}
, + }, + { + title: ( + <> + {SSRange.map((v) => ( +
{v}
+ ))} + + ), + dataIndex: 'SS', + key: 'SS', + width: '9rem', + align: 'center', + children: [ + { + title: '成人', + dataIndex: ['adult_cost'], + key: 'adult_cost', + width: '9rem', + align: 'center', + onCell: (r) => { + const text = r?.adult_cost; + const _warning = r.info?.isCityRow !== true && text && r?.unit_id !== '0'; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r?.unit_id !== '0'; + // && r?.unit_id !== r.unit_id; + + return ( + <> + {text} + {_warning ? ` /${r.unit_name}` : ''} + + ); + }, + }, + { + title: '儿童', + dataIndex: ['child_cost'], + key: 'child_cost', + width: '9rem', + align: 'center', + onCell: (r) => { + const text = r?.adult_cost; + const _warning = r.info?.isCityRow !== true && text && r?.unit_id !== '0'; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r?.unit_id !== '0'; + // && r?.unit_id !== r.unit_id; + + return ( + <> + {text} + {_warning ? ` /${r.unit_name}` : ''} + + ); + }, + }, + ], + }, + ]} + /> + + ); + }; + + const renderTable_B = () => { + // console.log('BBBBBBBBBBBB'); + if (!('B' in agencyProducts)) { + return null; + } + const tablesQuote = splitTable_B(use_year, agencyProducts.B); + // console.log(tablesQuote) + return ( + <> + {tablesQuote.map(({ cols, colsKey, data }, ti) => ( +
{ + return { rowSpan: record.rowSpan }; + }, + }, + { + title: '往返公里数', + dataIndex: ['info', 'km'], + key: 'product_title', + width: '6rem', + maxWidth: '6rem', + className: 'max-w-4', + onCell: (record) => { + return { rowSpan: record.rowSpan }; + }, + }, + { + title: '时段', + dataIndex: 'dateText', + key: 'dateText', + width: '5rem', + fixed: 'left', + render: (_, { quote_season, use_dates_start, use_dates_end, rowSpan }) => + quote_season === 'SS' ? ( + rowSpan > 1 ? ( +
+ 平季
(除特殊时段外)
+
+ ) : ( + '' + ) + ) : ( +
+ 特殊时段 +
+ {use_dates_start.replace(`${use_year}.`, '')}~{use_dates_end.replace(`${use_year}.`, '')} +
+
+ ), + }, + ...cols.map((col) => ({ + title: formatGroupSize(...col), + // dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: col[0], + children: [ + { + title: '成人', + dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: 'adult_cost', + width: '4rem', + onCell: (r) => { + const text = r[formatGroupSize(...col)]?.adult_cost; + const _warning = r.info?.isCityRow !== true && text && r[formatGroupSize(...col)]?.unit_id !== '0'; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r[formatGroupSize(...col)]?.unit_id !== '0'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + { + title: '儿童', + dataIndex: [formatGroupSize(...col), 'child_cost'], + key: 'child_cost', + width: '4rem', + onCell: (r) => { + const text = r[formatGroupSize(...col)]?.child_cost; + const _warning = r.info?.isCityRow !== true && text && r[formatGroupSize(...col)]?.unit_id !== '0'; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r[formatGroupSize(...col)]?.unit_id !== '0'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + ], + })), + ]} + dataSource={data} + size={'small'} + pagination={false} + scroll={{ x: 'max-content' }} + /> + ))} + + ); + }; + + const renderTable_J = () => { + // console.clear(); + // console.log('jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj'); + if (!('J' in agencyProducts)) { + return null; + } + const tablesQuote = splitTable_J(use_year, agencyProducts.J); + // console.log(tablesQuote) + return ( + <> + {tablesQuote.map(({ cols, colsKey, data }, ti) => ( +
{ + return { rowSpan: record.rowSpan }; + }, + }, + { + title: '时段', + dataIndex: 'dateText', + key: 'dateText', + width: '9rem', + fixed: 'left', + render: (_, { quote_season, use_dates_start, use_dates_end, rowSpan }) => + quote_season === 'SS' ? ( + rowSpan > 1 ? ( +
+ 平季
(除特殊时段外)
+
+ ) : ( + '' + ) + ) : ( +
+ 特殊时段 +
+ {use_dates_start.replace(`${use_year}.`, '')}~{use_dates_end.replace(`${use_year}.`, '')} +
+
+ ), + }, + ...cols.map((col) => ({ + title: formatGroupSize(...col), + // dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: col[0], + children: [ + { + title: '成人', + dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: 'adult_cost', + width: '4rem', + onCell: (r, ) => { + const text = r[formatGroupSize(...col)]?.adult_cost + const _warning = r.info?.isCityRow !== true + && text + && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + { + title: '儿童', + dataIndex: [formatGroupSize(...col), 'child_cost'], + key: 'child_cost', + width: '4rem', + onCell: (r, ) => { + const text = r[formatGroupSize(...col)]?.child_cost + const _warning = r.info?.isCityRow !== true + && text + && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + ], + })), + ]} + dataSource={data} + size={'small'} + pagination={false} + scroll={{ x: 'max-content' }} + // className='mt-4' + /> + ))} + + ); + }; + + const renderTable_D = () => { + // console.clear(); + // console.log('DDDDDDDDDDDDDDDDDDDDDDDDDDDDDD'); + if (!('D' in agencyProducts)) { + return null; + } + const tablesQuote = splitTable_D(use_year, agencyProducts.D); + // console.log(tablesQuote) + return ( + <> + {tablesQuote.map(({ cols, colsKey, data }, ti) => ( +
{ + return { rowSpan: record.rowSpan }; + }, + }, + { + title: '时段', + dataIndex: 'dateText', + key: 'dateText', + width: '9rem', + fixed: 'left', + render: (_, { quote_season, use_dates_start, use_dates_end, rowSpan }) => + quote_season === 'SS' ? ((rowSpan === 1) ? '' : ( +
+ 平季
(除特殊时段外)
+
+ {use_dates_start.replace(`${use_year}.`, '')}~{use_dates_end.replace(`${use_year}.`, '')} +
+
+ )) : ( +
+ 特殊时段 +
+ {use_dates_start.replace(`${use_year}.`, '')}~{use_dates_end.replace(`${use_year}.`, '')} +
+
+ ), + }, + ...cols.map((col) => ({ + title: formatGroupSize(...col), + // dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: col[0], + // children1: [ + // { title: '成人', dataIndex: [formatGroupSize(...col), 'adult_cost'], key: 'adult_cost' }, + // { title: '儿童', dataIndex: [formatGroupSize(...col), 'child_cost'], key: 'child_cost' }, + // ], + children: [ + { + title: '成人', + dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: 'adult_cost', + width: '4rem', + onCell: (r, ) => { + const text = r[formatGroupSize(...col)]?.adult_cost + const _warning = r.info?.isCityRow !== true + && text + && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + { + title: '儿童', + dataIndex: [formatGroupSize(...col), 'child_cost'], + key: 'child_cost', + width: '4rem', + onCell: (r, ) => { + const text = r[formatGroupSize(...col)]?.child_cost + const _warning = r.info?.isCityRow !== true + && text + && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + ] + })), + ]} + dataSource={data} + size={'small'} + pagination={false} + scroll={{ x: 'max-content' }} + // className='mt-4' + /> + ))} + + ); + }; + + /** + * 导游 + */ + const renderTable_Q = () => { + // console.log('QQQQQQ'); + if (!('Q' in agencyProducts)) { + return null; + } + const tablesQuote = splitTable_Q(use_year, agencyProducts.Q); + // console.log('tablesQuote', tablesQuote); + return ( + <> +
{ + // return { rowSpan: record.rowSpan }; + // }, + }, + { + title: '平季(除特殊时段外)', + dataIndex: 'SS', + key: 'SS', + width: '9rem', + children: [ + { + title: '成人', + dataIndex: [0, 'adult_cost'], + key: 'adult_cost', + width: '9rem', align: 'center', + render: (_, { SS }) => ( +
+ {(SS || []).length === 1 ? ( +
+ {SS[0].adult_cost}{SS[0].unit_id !== '1' ? ` /${SS[0].unit_name}` : ''} +
+ ) : ( + (SS || []).map((ele, qi) => ( +
+
+ + {formatGroupSize(ele.group_size_min, ele.group_size_max, true)}, {ele.unit_name} + + {ele.adult_cost} +
+
+ )) + )} +
+ ), + }, + { + title: '儿童', + dataIndex: [0, 'child_cost'], + key: 'child_cost', + width: '9rem', align: 'center', + render: (_, { SS }) => ( +
+ {(SS || []).length === 1 ? ( +
+ {SS[0].child_cost}{SS[0].unit_id !== '1' ? ` /${SS[0].unit_name}` : ''} +
+ ) : ( + (SS || []).map((ele, qi) => ( +
+
+ + {formatGroupSize(ele.group_size_min, ele.group_size_max, true)}, {ele.unit_name} + + {ele.child_cost} +
+
+ )) + )} +
+ ), + }, + ], + }, + { + title: '旺季', + dataIndex: 'PS', + key: 'PS', + width: '9rem', + children: [ + { + title: '成人', + dataIndex: [0, 'adult_cost'], + key: 'adult_cost', + width: '9rem', align: 'center', + render: (_, { PS }) => ( +
+ {(PS || []).map((ele, pi) => ( +
+
+ {ele.rows.map((d, di) => ( +
+ {d.use_dates_start.replace(`${use_year}.`, '')}~{d.use_dates_end.replace(`${use_year}.`, '')} +
+ ))} +
+ {ele.adult_cost}{ele.unit_id !== '1' ? ` /${ele.unit_name}` : ''} +
+ ))} +
+ ), + // // 表格形式 + // render: (_, { PSData }) => ( + //
+ // {(PSData || []).map((ele, pi) => ( + //
+ //
+ // {ele.headerDates.map((d, di) => ( + //
+ // {d.use_dates_start.replace(`${use_year}.`, '')}~{d.use_dates_end.replace(`${use_year}.`, '')} + //
+ // ))} + //
+ // {ele.rows.map((qrow, qri) => ( + //
+ // {formatGroupSize(qrow.group_size_min, qrow.group_size_max, true)} + // {qrow.unit_name} + // {qrow.adult_cost} + //
+ // ))} + //
+ //
+ // ))} + //
+ // ), + }, + { + title: '儿童', + dataIndex: [0, 'child_cost'], + key: 'child_cost', + width: '9rem', align: 'center', + render: (_, { PS }) => ( +
+ {(PS || []).map((ele, pi) => ( +
+
+ {ele.rows.map((d, di) => ( +
+ {d.use_dates_start.replace(`${use_year}.`, '')}~{d.use_dates_end.replace(`${use_year}.`, '')} +
+ ))} +
+ {ele.child_cost}{ele.unit_id !== '1' ? ` /${ele.unit_name}` : ''} +
+ ))} +
+ ), + }, + ], + }, + ]} + /> + + ); + }; + + /** + * 景点 + */ + const renderTable_7 = () => { + // console.log('7777777777777'); + if (!('7' in agencyProducts)) { + return null; + } + const tablesQuote = splitTable_7(use_year, agencyProducts['7']); + // console.log('tableQuote', tablesQuote) + return ( + <> +
{ + // return { rowSpan: record.rowSpan }; + // }, + }, + { + title: ( + <> + 平季(除特殊时段外) +
+ - + 成人 + 儿童 +
+ + ), + dataIndex: 'SS', + key: 'SS', + width: '9rem', + render: (_, { SS }) => ( +
+ {(SS || []).length === 1 ? ( +
+ {SS[0].unit_id === '0' && SS[0].group_size_min <= 1 && SS[0].group_size_max === 1000 ? ( + + ) : ( + <> + {formatGroupSize(SS[0].group_size_min, SS[0].group_size_max, true)} + {SS[0].unit_name} + + )} + {SS[0].adult_cost} + {SS[0].child_cost} +
+ ) : ( + (SS || []).map((ele, qi) => ( +
+
+ {formatGroupSize(ele.group_size_min, ele.group_size_max, true)} + {ele.unit_name} + {ele.adult_cost} + {ele.child_cost} +
+
+ )) + )} +
+ ), + }, + { + title: ( + <> + 旺季 +
+ - + 成人 + 儿童 +
+ + ), + dataIndex: 'PS', + key: 'PS', + width: '9rem', + align: 'center', + render: (_, { PSData }) => ( +
+ {(PSData || []).map((ele, pi) => ( +
+
+ {ele.headerDates.map((d, di) => ( +
+ {d.use_dates_start.replace(`${use_year}.`, '')}~{d.use_dates_end.replace(`${use_year}.`, '')} +
+ ))} +
+ {ele.rows.map((qrow, qri) => ( +
+ {formatGroupSize(qrow.group_size_min, qrow.group_size_max, true)} + {qrow.unit_name} + {qrow.adult_cost} + {qrow.child_cost} +
+ ))} +
+
+ ))} +
+ ), + }, + { + title: '特殊项目', + width: '9rem', + key: 'extras', + render: (_, r) => { + const _extras = extras[r.info.id] || []; + return ( +
+ {_extras.map((e) => ( +
+ {e.info.product_title}【{productsTypesMapVal[e.info.product_type_id]?.label || (e.info.product_type_name)}】 +
+ ))} +
+ ); + }, + }, + ]} + /> + + ); + }; + + const renderTable_R = () => { + // console.log('RRRRRRRRRRRRR'); + if (!('R' in agencyProducts)) { + return null; + } + const tablesQuote = splitTable_R(use_year, agencyProducts.R); + return ( + <> + {tablesQuote.map(({ cols, colsKey, data }, ti) => ( +
{ + return { rowSpan: record.rowSpan }; + }, + }, + { + title: '时段', + dataIndex: 'dateText', + key: 'dateText', + width: '5rem', + fixed: 'left', + render: (_, { quote_season, use_dates_start, use_dates_end, rowSpan }) => + quote_season === 'SS' ? ( + rowSpan > 1 ? ( +
+ 平季
(除特殊时段外)
+
+ ) : ( + '' + ) + ) : ( +
+ 特殊时段 +
+ {use_dates_start.replace(`${use_year}.`, '')}~{use_dates_end.replace(`${use_year}.`, '')} +
+
+ ), + }, + ...cols.map((col) => ({ + title: formatGroupSize(...col), + // dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: col[0], + children: [ + { + title: '成人', + dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: 'adult_cost', + width: '4rem', + onCell: (r) => { + const text = r[formatGroupSize(...col)]?.adult_cost + const _warning = r.info?.isCityRow !== true + && text + && r[formatGroupSize(...col)]?.unit_id !== '0'; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r[formatGroupSize(...col)]?.unit_id !== '0'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + { + title: '儿童', + dataIndex: [formatGroupSize(...col), 'child_cost'], + key: 'child_cost', + width: '4rem', + onCell: (r) => { + const text = r[formatGroupSize(...col)]?.child_cost + const _warning = r.info?.isCityRow !== true + && text + && r[formatGroupSize(...col)]?.unit_id !== '0'; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text + && r[formatGroupSize(...col)]?.unit_id !== '0'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + ], + })), + { + title: '司陪餐补(元/团/餐)', + dataIndex: 'extra', + key: 'extra', + width: '4rem', + onCell: (record, index) => { + return { rowSpan: index === 0 ? data.length : 0 }; + }, + render: (_) => <>不分人等, + }, + ]} + dataSource={data} + size={'small'} + pagination={false} + scroll={{ x: 'max-content' }} + /> + ))} + + ); + }; + + const renderTable_8 = () => { + // console.clear(); + // console.log('888888'); + if (!('8' in agencyProducts)) { + return null; + } + const tablesQuote = splitTable_8(use_year, agencyProducts['8']); + // console.log('---- tablesQuote ----', tablesQuote) + return ( + <> + {tablesQuote.map(({ cols, colsKey, data }, ti) => ( +
{ + return { rowSpan: record.rowSpan }; + }, + }, + { + title: '时段', + dataIndex: 'dateText', + key: 'dateText', + width: '8rem', + fixed: 'left', + render: (_, { quote_season, use_dates_start, use_dates_end, rowSpan }) => + quote_season === 'SS' ? ( + rowSpan > 1 ? ( +
+ 平季
(除特殊时段外)
+
+ ) : ( + '' + ) + ) : ( +
+ 特殊时段 +
+ {use_dates_start.replace(`${use_year}.`, '')}~{use_dates_end.replace(`${use_year}.`, '')} +
+
+ ), + }, + ...cols.map((col) => ({ + title: formatGroupSize(...col), + // dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: col[0], + children: [ + { + title: '成人', + dataIndex: [formatGroupSize(...col), 'adult_cost'], + key: 'adult_cost', + width: '4rem', + className: 'max-w-16', + onCell: (r) => { + const text = r[formatGroupSize(...col)]?.adult_cost; + const _warning = r.info?.isCityRow !== true && text && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text && r[formatGroupSize(...col)]?.unit_id !== '1'; + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + { + title: '儿童', + dataIndex: [formatGroupSize(...col), 'child_cost'], + key: 'child_cost', + width: '4rem', + className: 'max-w-16', + onCell: (r) => { + const text = r[formatGroupSize(...col)]?.child_cost; + const _warning = r.info?.isCityRow !== true && text && r[formatGroupSize(...col)]?.unit_id !== '1'; + // && r[formatGroupSize(...col)]?.unit_id !== r.unit_id; + + return { className: _warning ? 'bg-yellow-100' : '' }; + }, + render: (text, r) => { + const _warning = r.info?.isCityRow !== true && text && r[formatGroupSize(...col)]?.unit_id !== '1'; + return ( + <> + {text} + {_warning ? ` /${r[formatGroupSize(...col)].unit_name}` : ''} + + ); + }, + }, + ], + })), + ]} + dataSource={data} + size={'small'} + pagination={false} + scroll={{ x: 'max-content' }} + /> + ))} + + ); + } + + const typeTableMap = { + // '6': { render: () => {} }, + '6': { render: renderTable_6 }, + // 'B': { render: () => {} }, + 'B': { render: renderTable_B }, + // 'J': { render: () => {} }, + 'J': { render: renderTable_J }, + // 'Q': { render: () => {} }, + 'Q': { render: renderTable_Q }, + // '7': { render: () => {} }, + '7': { render: renderTable_7 }, + // 'R': { render: () => {} }, + 'R': { render: renderTable_R }, + // '8': { render: () => {} }, + '8': { render: renderTable_8 }, + // 'D': { render: () => {} }, + 'D': { render: renderTable_D }, + }; + + const renderTableByType = (allTables) => { + return allTables.map(({ typeKey, tables }) => { + return ( + +
+ {typeTableMap[typeKey.value].render()} +
+
+ ); + }); + }; + + return ( + <> + + +
+ {t('Preview')} + +
+ + } + width={'70%'} + open={previewMode} + onClose={() => setPreviewMode(false)}> +
{renderTableByType(tables)}
+
+ + ); +}; +export default AgencyPreview; diff --git a/src/views/products/Print/ExportDocxBtn.jsx b/src/views/products/Print/ExportDocxBtn.jsx new file mode 100644 index 0000000..b58bb4b --- /dev/null +++ b/src/views/products/Print/ExportDocxBtn.jsx @@ -0,0 +1,65 @@ +import { Button } from 'antd'; +import { useProductsAuditStatesMapVal, useProductsTypesMapVal } from '@/hooks/useProductsSets'; +import { useTranslation } from 'react-i18next'; +import useProductsStore, { getAgencyAllExtrasAction } from '@/stores/Products/Index'; +import RequireAuth from '@/components/RequireAuth'; +import { PERM_PRODUCTS_OFFER_AUDIT } from '@/config'; +import dayjs from 'dayjs'; + +import AgencyContract from '../Print/AgencyContract'; +import { saveAs } from 'file-saver'; +import { Packer } from 'docx'; +import { isEmpty } from '@/utils/commons'; + +const ExportDocxBtn = ({ params = { travel_agency_id: '', use_year: '', audit_state: '' }, ...props }) => { + const { t } = useTranslation(); + const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]); + const [activeAgency] = useProductsStore((state) => [state.activeAgency]); + const { travel_agency_id, use_year, audit_state } = params; + + const auditStatesMap = useProductsAuditStatesMapVal(); + const productsTypesMapVal = useProductsTypesMapVal(); + + const { getRemarkList } = useProductsStore((selector) => ({ + getRemarkList: selector.getRemarkList, + })); + const handleDownload = async () => { + // await refresh(); + const _agencyExtras = await getAgencyAllExtrasAction(params); + const agencyExtras = Object.keys(_agencyExtras).reduce((acc, pid) => { + const pitemExtras = _agencyExtras[pid]; + const _pitem = (pitemExtras || []).map(eitem => ({ ...eitem, info: { ...eitem.info, product_type_name_txt: productsTypesMapVal[eitem.info.product_type_id]?.label || eitem.info.product_type_name } } )); + return { ...acc, [pid]: _pitem }; + }, {}); + const remarks = await getRemarkList(); + const remarksMappedByType = remarks.reduce((r, v) => ({ ...r, [v.product_type_id]: v }), {}); + const documentCreator = new AgencyContract(); + const doc = documentCreator.create([ + params, + activeAgency, + agencyProducts, + agencyExtras, + // remarks, + remarksMappedByType, + ]); + + const _d = dayjs().format('YYYYMMDD_HH.mm.ss.SSS'); // Date.now().toString(32) + // console.log(params); + const _state = isEmpty(audit_state) ? '' : auditStatesMap[audit_state].label; + Packer.toBlob(doc).then((blob) => { + saveAs(blob, `${activeAgency.travel_agency_name}${use_year}年地接合同-${_state}-${_d}.docx`); + }); + }; + return ( + <> + {/* todo: export, 审核完成之后才能导出 */} + + + {/* */} + + + ); +}; +export default ExportDocxBtn; diff --git a/src/views/reservation/Detail.jsx b/src/views/reservation/Detail.jsx index 59b99d1..e344063 100644 --- a/src/views/reservation/Detail.jsx +++ b/src/views/reservation/Detail.jsx @@ -1,13 +1,12 @@ -import { useParams } from 'react-router-dom' +import { useParams, useNavigate } from 'react-router-dom' import { useEffect, useState } from 'react' -import { Row, Col, Space, Button, Table, Input, Typography, Modal, Tag, App } from 'antd' +import { Row, Col, Space, Button, Table, Input, Typography, Modal, Tag, App, Flex } from 'antd' import { - FileOutlined + FileOutlined, ArrowLeftOutlined } from '@ant-design/icons' import { usingStorage } from '@/hooks/usingStorage' import useReservationStore from '@/stores/Reservation' import { useTranslation } from 'react-i18next' -import BackBtn from '@/components/BackBtn' const { Title, Paragraph } = Typography const { TextArea } = Input @@ -71,6 +70,7 @@ function Detail() { ); } + const navigate = useNavigate(); const [isModalOpen, setIsModalOpen] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); const [confirmText, setConfirmText] = useState(''); @@ -161,14 +161,7 @@ function Detail() { /> - - - {t('group:RefNo')}: {reservationDetail.referenceNumber}; {t('group:ArrivalDate')}: {reservationDetail.arrivalDate}; - - - - - +