From cb0f519adf79909997dcc60b3b3234c579ed1ac1 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 30 Aug 2024 11:05:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=BC=E5=87=BA=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E5=90=88=E5=90=8C=20.docx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Products/Index.js | 7 + src/utils/commons.js | 7 + src/views/products/Audit.jsx | 2 +- src/views/products/Detail.jsx | 2 +- src/views/products/Detail/Header.jsx | 13 +- src/views/products/Print/AgencyContract.jsx | 811 +++++++++++++++++++- 6 files changed, 814 insertions(+), 28 deletions(-) diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 45307c6..7e51280 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -35,6 +35,13 @@ export const getAgencyProductsAction = async (param) => { return errcode !== 0 ? { agency: {}, products: [] } : result; }; +export const getAgencyAllExtrasAction = async (param) => { + const _param = { ...param, use_year: String(param.use_year || '').replace('all', ''), }; + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_products_extras`, _param); + const extrasMapped = result.reduce((acc, curr) => ({...acc, [curr.product_id]: curr.extras}), {}); + return errcode !== 0 ? {} : extrasMapped; +} + /** * @param {object} body { id, travel_agency_id, extras: [{id, title, code}] } */ diff --git a/src/utils/commons.js b/src/utils/commons.js index 0e93cc5..c343b05 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -162,6 +162,13 @@ export const sortArrayByOrder = (items, keyName, keyOrder) => { return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]); }); }; + +export function unique(arr) { + const x = new Set(arr); + return [...x]; +} +export const uniqWith = (arr, fn) => arr.filter((element, index) => arr.findIndex((step) => fn(element, step)) === index); + /** * 合并Object, 递归地 */ diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 3a146eb..3fb2941 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -172,7 +172,7 @@ const Audit = ({ ...props }) => { const [loading, setLoading] = useProductsStore(state => [state.loading, state.setLoading]); const { travelAgencyId } = usingStorage(); - const handleGetAgencyProducts = ({pick_year, pick_agency, pick_state}={}) => { + const handleGetAgencyProducts = async ({pick_year, pick_agency, pick_state}={}) => { const year = pick_year || use_year || dayjs().year(); const agency = pick_agency || travel_agency_id || travelAgencyId; const state = pick_state ?? audit_state; diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index e28175e..d9ea10d 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -20,7 +20,7 @@ function Detail() { const [loading, setLoading] = useProductsStore((state) => [state.loading, state.setLoading]); const { travelAgencyId } = usingStorage(); - const handleGetAgencyProducts = ({ pick_year, pick_agency, pick_state } = {}) => { + const handleGetAgencyProducts = async ({ pick_year, pick_agency, pick_state } = {}) => { const year = pick_year || use_year || switchParams.use_year || dayjs().year(); const agency = pick_agency || travel_agency_id || travelAgencyId; const state = pick_state ?? audit_state; diff --git a/src/views/products/Detail/Header.jsx b/src/views/products/Detail/Header.jsx index 0964917..796ec99 100644 --- a/src/views/products/Detail/Header.jsx +++ b/src/views/products/Detail/Header.jsx @@ -4,7 +4,7 @@ import { App, Button, Divider, Popconfirm, Select } from 'antd'; import { ReloadOutlined } from '@ant-design/icons'; import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import { useTranslation } from 'react-i18next'; -import useProductsStore, { postAgencyProductsAuditAction, postAgencyAuditAction } from '@/stores/Products/Index'; +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'; @@ -30,7 +30,7 @@ const Header = ({ refresh, ...props }) => { const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]); const [switchParams, setSwitchParams] = useProductsStore((state) => [state.switchParams, state.setSwitchParams]); // const [activeAgencyState] = useProductsStore((state) => [state.activeAgencyState]); - // const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]); + const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]); const stateMapVal = useProductsAuditStatesMapVal(); const { message, notification } = App.useApp(); const navigate = useNavigate(); @@ -129,12 +129,15 @@ const Header = ({ refresh, ...props }) => { }; - const handleDownload = () => { + const handleDownload = async () => { + // await refresh(); + const agencyExtras = await getAgencyAllExtrasAction(switchParams); const documentCreator = new AgencyContract(); - const doc = documentCreator.create([]); + const doc = documentCreator.create([switchParams, activeAgency, agencyProducts, agencyExtras]); + const _d = dayjs().format('YYYYMMDD_HH.mm.ss.SSS'); // Date.now().toString(32) Packer.toBlob(doc).then(blob => { - saveAs(blob, `${pickYear}地接合同-${Date.now().toString(32)}.docx`); + saveAs(blob, `${activeAgency.travel_agency_name}${pickYear}年地接合同-${_d}.docx`); }); }; diff --git a/src/views/products/Print/AgencyContract.jsx b/src/views/products/Print/AgencyContract.jsx index 96f9ce6..1bdc2d1 100644 --- a/src/views/products/Print/AgencyContract.jsx +++ b/src/views/products/Print/AgencyContract.jsx @@ -4,9 +4,7 @@ import { Header, Footer, HeadingLevel, - Packer, Paragraph, - TabStopPosition, TabStopType, Table, TableCell, @@ -16,7 +14,118 @@ import { NumberFormat, PageNumber, WidthType, + LevelFormat, + Tab, + PositionalTab, + PositionalTabAlignment, + PositionalTabRelativeTo, + PositionalTabLeader, } from 'docx'; +import dayjs from 'dayjs'; +import { cloneDeep, groupBy, isEmpty, unique, uniqWith } from '@/utils/commons'; + +// 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 chunkBy = (use_year, dataList = [], by = []) => { + const groupSize = dataList.reduce((a, c) => a.concat((c.quotation || []).reduce((aq, cq) => aq.concat([[cq.group_size_min, cq.group_size_max]]), [])), []); + const allSizeCut = groupBy( + uniqWith(groupSize, (a, b) => a[0] === b[0] && a[1] === b[1]), + (c) => c[0] + ); + const sizeSets = Object.keys(allSizeCut).map((s1) => [Number(s1), Math.max(...allSizeCut[s1].map((c) => c[1]))]); + const sizeSetsByMax = uniqWith(sizeSets, (a, b) => a[1] === b[1]); + // const sizeSetsByMax = [[1,4], [5,6], [7, 9], [10, Infinity]]; // test: 固定车辆座次 + + const dataRoll = dataList.map((rowp, ii) => { + const quotation = rowp.quotation.map((quoteItem) => { + const matchRange = cloneDeep(sizeSetsByMax) + .reverse() + .find((ss) => quoteItem.group_size_min >= ss[0] && quoteItem.group_size_max <= ss[1]); + // const matchMax = + // matchRange || + // cloneDeep(sizeSetsByMax) + // // .reverse() + // .find((ss) => quoteItem.group_size_max <= ss[1]); + // const matchMin = + // matchMax || + // cloneDeep(sizeSetsByMax) + // // .reverse() + // .find((ss) => quoteItem.group_size_min >= ss[0]); + // const inSet = matchMin; + const inSet = matchRange; + return { + ...quoteItem, + quote_season: isFullYearOrLonger(use_year, quoteItem.use_dates_start, quoteItem.use_dates_end) ? 'SS' : 'PS', + quote_size: inSet ? inSet.join('-') : '10-Infinity', + }; + }); + return { ...rowp, quotation }; + }); + + const chunk = dataRoll.map((rowp) => { + const quote_chunk = groupBy(rowp.quotation, (quoteItem) => by.map((key) => quoteItem[key]).join('@')); + return { ...rowp, quote_chunk }; + }); + const dataSource = chunk.map((rowp, ri) => { + const thisChunk = Object.keys(rowp.quote_chunk).reduce((qc, ckey) => { + const ckeyArr = ckey.split('@'); + if (isEmpty(qc[ckeyArr[0]])) { + qc[ckeyArr[0]] = ckeyArr[1] ? { [ckeyArr[1]]: rowp.quote_chunk[ckey] } : rowp.quote_chunk[ckey]; + } else { + qc[ckeyArr[0]][ckeyArr[1]] = (qc[ckeyArr[0]][ckeyArr[1]] || []).concat(rowp.quote_chunk[ckey]); + } + return qc; + }, {}); + + return { ...rowp, quote_chunk: thisChunk }; + }); + const allquotation = chunk.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}`)); + // console.log(chunk, dataSource, sizeSets, sizeSetsByMax); + return { chunk, dataSource, sizeSets: sizeSetsByMax, SSRange, PSRange }; +}; + const DOC_TITLE = '地接合同'; const pageMargins = { top: `15mm`, @@ -38,10 +147,18 @@ const tableBorderOne = { }; export default class AgencyContract { - create([data]) { + create([headerParams, activeAgency, agencyProducts, agencyExtras]) { + const { use_year } = headerParams; + const h1 = `${activeAgency.travel_agency_name}${use_year}年${DOC_TITLE}`; + const yearStart = dayjs(`${use_year}-01-01`).format('YYYY.MM.DD'); + const yearEnd = dayjs(`${use_year}-12-31`).format('YYYY.MM.DD'); + // console.log(agencyExtras); + const document = new Document({ creator: 'China Highlights', - title: DOC_TITLE, + title: h1, + subject: '2025桂林海纳价格合同模板', + // description: '', styles: { paragraphStyles: [ { @@ -49,13 +166,31 @@ export default class AgencyContract { name: 'Normal', quickFormat: true, run: { - size: 20, + size: 22, font: { name: '宋体' }, color: '000000', }, paragraph: { spacing: { - after: 0, + after: 10, + }, + }, + }, + { + id: 'Title', + name: 'Title', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 44, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 200, + after: 200, }, }, }, @@ -72,7 +207,8 @@ export default class AgencyContract { }, paragraph: { spacing: { - after: 120, + before: 200, + after: 200, }, }, }, @@ -83,17 +219,53 @@ export default class AgencyContract { next: 'Normal', quickFormat: true, run: { - size: 24, + size: 28, font: { name: '宋体' }, color: '000000', }, paragraph: { spacing: { before: 120, - // after: 120, + after: 120, }, }, }, + { + id: 'TableHeader', + name: 'Table Header', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 22, + font: { name: '宋体' }, + color: '000000', + bold: true, + }, + paragraph: { + spacing: { + before: 80, + after: 80, + }, + }, + }, + // { + // id: "MySpectacularStyle", + // name: "My Spectacular Style", + // basedOn: "Heading1", + // next: "Heading1", + // quickFormat: true, + // run: { + // italics: true, + // color: "990000", + // }, + // }, + ], + }, + numbering: { + config: [ + { reference: 'products-type', levels: [{ level: 0, text: '%1、', format: LevelFormat.CHINESE_COUNTING }] }, + { reference: 'terms', levels: [{ level: 0, text: '%1.', format: LevelFormat.DECIMAL }] }, ], }, sections: [ @@ -109,7 +281,10 @@ export default class AgencyContract { }, headers: { default: new Header({ - children: [this.createPageHeaderText(`${new Date().toLocaleString()}系统生成`)], + children: [ + this.createPageHeaderText(`Email: xeniayou@hainatravel.com`, { italics: false }), + this.createPageHeaderText(`https://www.chinahighlights.com`, { italics: false }), + ], }), }, footers: { @@ -137,17 +312,47 @@ export default class AgencyContract { size: 20, }), ], - }) + }), ], }), }, children: [ - new Paragraph({ - text: DOC_TITLE, - heading: HeadingLevel.HEADING_1, - alignment: 'center', - }), - this.createSubHeading(`副标题`), + // new Paragraph({ + // text: h1, + // heading: HeadingLevel.HEADING_1, + // alignment: 'center', + // }), + this.createTitle(h1), + this.createSubHeading(`价格有效期: ${yearStart}-${yearEnd}`, { alignment: AlignmentType.CENTER }), + + ...(isEmpty(agencyProducts['6']) + ? [] + : [this.createSubHeading(`综费`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_6(use_year, agencyProducts['6'])]), + ...(isEmpty(agencyProducts['B']) + ? [] + : [this.createSubHeading(`超公里`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_B(use_year, agencyProducts['B'])]), + ...(isEmpty(agencyProducts['J']) + ? [] + : [this.createSubHeading(`车费`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_J(use_year, agencyProducts['J'])]), + ...(isEmpty(agencyProducts['Q']) + ? [] + : [this.createSubHeading(`导游`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_6(use_year, agencyProducts['Q'])]), + // this.createTable_Q(use_year, agencyProducts['Q']), + ...(isEmpty(agencyProducts['7']) + ? [] + : [this.createSubHeading(`景点`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_7(use_year, agencyProducts['7'], agencyExtras)]), + ...(isEmpty(agencyProducts['R']) + ? [] + : [this.createSubHeading(`餐费`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_R(use_year, agencyProducts['R'], agencyExtras)]), + ...(isEmpty(agencyProducts['D']) + ? [] + : [this.createSubHeading(`包价线路`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_J(use_year, agencyProducts['D'])]), + this.createSubHeading(`公司银行账户`, { numbering: { reference: 'products-type', level: 0 } }), + this.createInfo_Bank(), + this.createSubHeading(`联系资料`, { numbering: { reference: 'products-type', level: 0 } }), + this.createInfo_Contact(), + this.createSubHeading(`补充条款`, { numbering: { reference: 'products-type', level: 0 } }), + ...this.createInfo_Terms(), ], }, ], @@ -156,21 +361,584 @@ export default class AgencyContract { return document; } + /** + * 综费 + */ + createTable_6(use_year, dataList, style = {}) { + const { chunk, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season']); + + return new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [this.createTableHeader('项目', 30), this.createTableHeader(['平季', ...SSRange], 35), this.createTableHeader(['旺季', ...PSRange], 35)], + }), + ...chunk.map((row, ri) => this.createDetailRowSeason2(row, ri)).reduce((a, c) => a.concat(c), []), + + this.createTable_Memo(3), + ], + }); + } + /** + * 超公里 + */ + createTable_B(use_year, dataList) { + const { chunk: dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_size']); + const infoCol = (rowp) => + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${rowp?.info?.km}`, + alignment: AlignmentType.CENTER, + }), + ], + }); + + return new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + this.createTableHeader('超公里项目', 20), + this.createTableHeader('往返公里数', 20), + this.createTableHeader(['全年', ...SSRange, ...PSRange], 60, { columnSpan: sizeSets.length }), + ], + }), + new TableRow({ + children: [ + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })), + ], + }), + ...dataSource.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, { infoCol })).reduce((a, c) => a.concat(c), []), + this.createTable_Memo(2 + sizeSets.length), + ], + }); + } + + /** + * 车费 + */ + createTable_J(use_year, dataList) { + const { chunk, dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season', 'quote_size']); + + return new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + this.createTableHeader('项目', 20), + this.createTableHeader(['平季', ...SSRange], 40, { columnSpan: sizeSets.length }), + this.createTableHeader(['旺季', ...PSRange], 40, { columnSpan: sizeSets.length }), + ], + }), + new TableRow({ + children: [ + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })), + ...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })), + ], + }), + ...dataSource.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, { withSeason: true })).reduce((a, c) => a.concat(c), []), + + this.createTable_Memo(1 + sizeSets.length * 2), + ], + }); + } + /** + * 导游 + * @ignore + * @deprecated + */ + createTable_Q(use_year, dataList) {} + /** + * 景点 + */ + 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 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}`) + ); + // console.log(compactQuotation); + const thisE = new Paragraph({ + children: [new TextRun({ text: `${extra.info.product_title}`, 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}`; + return new Paragraph({ + children: [ + // new TextRun({ text: `${extra.info.product_title} ${firstQ.unit_name}`, bold: true }), + new TextRun({ + text: + `${firstQ.adult_cost}, ${firstQ.group_size_min}-${firstQ.group_size_max}人, ${firstQ.unit_name}` + + // + (`, ${firstQ.group_size_min}-${firstQ.group_size_max}人`) + (compactBy !== 'dates' ? `, ${date1}~${date2}${otherStr}` : ''), + }), + ], + }); + }); + return ac.concat([thisE, ...thisP]); + // return new Paragraph({ text: `${extra.info.product_title} ${extra.quotation?.[0]?.adult_cost || ''} ${extra.quotation?.[0]?.unit_name || ''}` }); + }, []), + ], + }); + }; + + return new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + this.createTableHeader('景点', 25), + this.createTableHeader(['平季', ...SSRange], 25), + this.createTableHeader(['旺季', ...PSRange], 25), + this.createTableHeader('附加费用', 25), + ], + }), + ...chunk.map((row, ri) => this.createDetailRowSeason2(row, ri, { extraCol })).reduce((a, c) => a.concat(c), []), + + this.createTable_Memo(1 + sizeSets.length * 2 + 1), + ], + }); + } + /** + * 餐费 + */ + 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 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: '' }) + ], + }); + + return new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + this.createTableHeader('社会餐厅午餐/晚餐', 25), + this.createTableHeader(['价格', ...SSRange, ...PSRange], 50, { columnSpan: sizeSets.length }), + this.createTableHeader('司陪餐补', 25), + ], + }), + new TableRow({ + children: [ + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })), + // new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, rowSpan: dataList.length+1, children: [new Paragraph({ text: '' })] }), + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ], + }), + ...chunk.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, { withSize: false, extraCol })).reduce((a, c) => a.concat(c), []), + // ...dataSource.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, true)).reduce((a, c) => a.concat(c), []), + + this.createTable_Memo(1 + sizeSets.length + 1), + ], + }); + } + /** + * 包价线路 + * @deprecated + */ + createTable_D(use_year, dataList) { + const { chunk, dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season', 'quote_size']); + + return new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + this.createTableHeader('项目', 30), + this.createTableHeader(['平季', ...SSRange], 35, { columnSpan: sizeSets.length }), + this.createTableHeader(['旺季', ...PSRange], 35, { columnSpan: sizeSets.length }), + ], + }), + new TableRow({ + children: [ + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })), + ...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })), + ], + }), + ...dataSource.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, { withSeason: true })).reduce((a, c) => a.concat(c), []), + + this.createTable_Memo(1 + sizeSets.length * 2), + ], + }); + } + + createDetailRowSize(sizeSets, rowp, ii, { withSeason = false, withSize = true, infoCol = () => {}, extraCol = () => {} } = {}) { + const sizeCol = sizeSets.map((ss) => ss.join('-')); + + return [ + new TableRow({ + children: [ + // new TableCell({ + // borders: tableBorderOne, + // rowSpan: 3, + // verticalAlign: AlignmentType.CENTER, + // children: [new Paragraph(String(ii + 1))], + // }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${rowp?.info?.product_title}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + infoCol(rowp), + ...sizeCol.map( + (ss) => + 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) => + new Paragraph({ + alignment: AlignmentType.LEFT, + text: isEmpty(quoteItem) + ? '' + : withSize + ? `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}` + : `${quoteItem.adult_cost}`, + }) + ), + // new Paragraph({ text: '-'+ii }) + ], + }) + ), + ...(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.LEFT, + // text: `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}${quoteItem.unit_name}`, + text: withSize + ? `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}` + : `${quoteItem.adult_cost}`, + }) + ), + ], + }) + ) + : []), + extraCol(rowp, ii), + ], + }), + ]; + } + createDetailRowSeason2(rowp, ii, { extraCol = () => {} } = {}) { + return [ + new TableRow({ + children: [ + // new TableCell({ + // borders: tableBorderOne, + // rowSpan: 3, + // verticalAlign: AlignmentType.CENTER, + // children: [new Paragraph(String(ii + 1))], + // }), + new TableCell({ + borders: tableBorderOne, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: `${rowp?.info?.product_title}`, + alignment: AlignmentType.LEFT, + }), + ], + }), + new TableCell({ + borders: tableBorderOne, + width: { size: 2000, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + ...(rowp.quote_chunk['SS'] || []).map( + (quoteItem, ii) => + new Paragraph({ + alignment: AlignmentType.LEFT, + text: `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}`, + }) + ), + ], + }), + new TableCell({ + borders: tableBorderOne, + width: { size: 2000, type: WidthType.DXA }, + verticalAlign: AlignmentType.CENTER, + children: [ + ...(rowp.quote_chunk['PS'] || []).map( + (quoteItem, ii) => + new Paragraph({ + alignment: AlignmentType.LEFT, + text: `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}`, + }) + ), + ], + }), + extraCol(rowp, ii), + ], + }), + ]; + } + + createTable_Memo(columnSpan) { + return new TableRow({ + children: [ + new TableCell({ + borders: tableBorderOne, + // verticalAlign: AlignmentType.CENTER, + columnSpan, + children: [new Paragraph(`备注: `), new Paragraph(``)], + }), + ], + }); + } + + createInfo_Bank() { + const Col1 = (label, opt = {}) => + new TableCell({ + borders: tableBorderOne, + width: { size: 2000, type: WidthType.DXA }, + // verticalAlign: AlignmentType.CENTER, + children: [new Paragraph(label)], + ...opt, + }); + return new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [Col1('帐户:'), new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] })], + }), + new TableRow({ + children: [Col1('银行:'), new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] })], + }), + new TableRow({ + children: [Col1('帐号:'), new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] })], + }), + new TableRow({ + children: [Col1('结账方式:'), new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] })], + }), + ], + }); + } + createInfo_Contact() { + const Col1 = (label, opt = {}) => + new TableCell({ + borders: tableBorderOne, + width: { size: 2000, type: WidthType.DXA }, + // verticalAlign: AlignmentType.CENTER, + children: [new Paragraph(label)], + ...opt, + }); + return new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [this.createTableHeader('联系人', 25), this.createTableHeader('电话', 25), this.createTableHeader('微信', 25), this.createTableHeader('手机', 25)], + }), + new TableRow({ + children: [Col1('经理:'), ...[null, null, null].map((col) => new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] }))], + }), + new TableRow({ + children: [Col1('计调:'), ...[null, null, null].map((col) => new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] }))], + }), + new TableRow({ + children: [Col1('财务(发票):'), ...[null, null, null].map((col) => new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] }))], + }), + ], + }); + } + createInfo_Terms() { + const subHeader = (text) => + new Paragraph({ + alignment: AlignmentType.CENTER, + outlineLevel: 1, + spacing: { after: 200, before: 200 }, + style: 'TableHeader', + children: [new TextRun({ text: `${text}`, bold: true, size: 28 })], + }); + const numberingContent = ([abstract, ...content], instance) => [ + new Paragraph({ text: abstract, indent: { firstLine: `2pc` } }), + ...content.map( + ([itemTitle, ...itemList]) => + new Paragraph({ + text: itemTitle, + numbering: { reference: 'terms', level: 0, instance }, + children: itemList.map((li) => new TextRun({ text: li, break: 1 })), + }) + ), + ]; + + return [ + subHeader('中华游专利产品接待权标准'), + ...numberingContent( + [ + '中华游是注重特色新产品开发的品牌,并希望通过开发新产品丰富中华游的网前线路,吸引更多的客户与中华游订购中国之旅。为了更好的鼓励各个地接社积极开发新产品、新线路,特制定中华游专利产品接待权标准,详情如下:', + ['新产品,新线路定义及专利期', '新产品,新线路非市场上现成的新产品,新体验,而是包含了独家设计或独家资源的产品和体验'], + ['中华游和地接社联合设计研发的新产品', '由中华游发起立意和产品框架', '地接社协助填充具体内容并落地', '专利期:6个月'], + ['地接社自主设计研发并落地(中华游或做细微调整)', '如:北京“民生探索”', '专利期:永久 (除非地接社已无法掌握产品或资源的独家性)'], + ], + 1 + ), + subHeader('关于价格变更'), + ...numberingContent( + [ + '中华游与各地接社签订年度地接协议后,将不认同中途涨价的要求。但考虑在实际合作中存在的不可控因素,为维护双方共同利益,两种价格变更情况处理方式如下', + [ + '常规价格变更:', + '一般景区和酒店会提前3个月通知价格变更事宜,要求地接社及时用聊天工具或邮件形式通知中华游产品采购,并以收到相同形式确认回执为准,否则中华游将有权拒付额外增加的一切费用。额外增加费用的承担规则为已确认团队计划保持原价,新价格仅适用于确认回复日期起的新发送团队计划。', + ], + [ + '临时价格变更:', + '一般属于政府行为或景区强制要求,如不立即交款则无法参观等紧急情况。因此种情况产生的额外费用要求第一时间通知中华游产品采购,并秉承维护双方共同利益与长期合作,共同协商友好解决方案。', + ], + ], + 2 + ), + new Paragraph({ + text: '签名盖章', + spacing: { before: 600, after: 200 }, + children: [ + new TextRun({ break: 2, children: ['甲方:', new Tab(), '乙方:桂林海纳国际旅行社有限公司'] }), + new TextRun({ break: 1, children: ['负责人签字盖章', new Tab(), '负责人签字盖章'] }), + new TextRun({ break: 3, children: ['联系人: ', new Tab(), '联系人:游佳佳'] }), + new TextRun({ break: 1, children: ['电话: ', new Tab(), '电话:13507831547'] }), + new TextRun({ break: 1, children: ['电子邮箱: ', new Tab(), '电子邮箱:xeniayou@hainatravel.com'] }), + new TextRun({ break: 1, children: [' 年 月 日', new Tab(), ' 年 月 日'] }), + ], + tabStops: [{ type: TabStopType.LEFT, position: 4800 }], + }), + // new Paragraph({ + // tabStops: [{ type: TabStopType.RIGHT, position: 1000 }], + // children: [ + // new TextRun({ + // children: [ + // '\t年', + // '\t\t月', + // '\t\t日', + // new PositionalTab({ + // alignment: PositionalTabAlignment.CENTER, + // relativeTo: PositionalTabRelativeTo.MARGIN, + // leader: PositionalTabLeader.NONE, + // }), + // // new Tab(), + // '\t年', + // '\t\t月', + // '\t\t日', + // ], + // }), + // ], + // }), + ]; + } + createTableHeader(text, w = null, opt = {}) { + const textP = typeof text === 'string' ? [text] : text; + return new TableCell({ + borders: tableBorderOne, + width: { size: w || `8mm`, type: w ? WidthType.PERCENTAGE : WidthType.AUTO }, + verticalAlign: AlignmentType.CENTER, + children: textP.map((t) => new Paragraph({ text: t, alignment: AlignmentType.CENTER, style: 'TableHeader' })), + ...opt, + }); + } createHeading(text) { return new Paragraph({ text: text, heading: HeadingLevel.HEADING_1, - thematicBreak: true, + // thematicBreak: true, + alignment: AlignmentType.CENTER, + }); + } + + createTitle(text) { + return new Paragraph({ + text: text, + heading: HeadingLevel.TITLE, + // thematicBreak: true, + alignment: AlignmentType.CENTER, }); } - createSubHeading(text) { + createSubHeading(text, style = {}) { return new Paragraph({ text: text, - heading: HeadingLevel.HEADING_2, + heading: HeadingLevel.HEADING_1, + ...style, }); } - createPageHeaderText(text) { + createPageHeaderText(text, style = {}) { return new Paragraph({ alignment: AlignmentType.RIGHT, children: [ @@ -178,6 +946,7 @@ export default class AgencyContract { text: text, italics: true, size: 20, + ...style, }), ], });