diff --git a/src/views/products/Detail/Header.jsx b/src/views/products/Detail/Header.jsx index 796ec99..0fe5e40 100644 --- a/src/views/products/Detail/Header.jsx +++ b/src/views/products/Detail/Header.jsx @@ -15,7 +15,7 @@ import VendorSelector from '@/components/VendorSelector'; import AuditStateSelector from '@/components/AuditStateSelector'; import NewProductModal from './NewProductModal'; -import AgencyContract from './../Print/AgencyContract'; +import AgencyContract from '../Print/AgencyContract_v3'; import { saveAs } from "file-saver"; import { Packer } from "docx"; diff --git a/src/views/products/Print/AgencyContract.jsx b/src/views/products/Print/AgencyContract_v0903.jsx similarity index 100% rename from src/views/products/Print/AgencyContract.jsx rename to src/views/products/Print/AgencyContract_v0903.jsx diff --git a/src/views/products/Print/AgencyContract_v3.jsx b/src/views/products/Print/AgencyContract_v3.jsx new file mode 100644 index 0000000..3ce91a0 --- /dev/null +++ b/src/views/products/Print/AgencyContract_v3.jsx @@ -0,0 +1,1013 @@ +import { + AlignmentType, + Document, + Header, + Footer, + HeadingLevel, + Paragraph, + TabStopType, + Table, + TableCell, + TableRow, + TextRun, + BorderStyle, + NumberFormat, + PageNumber, + WidthType, + LevelFormat, + Tab, + PositionalTab, + PositionalTabAlignment, + PositionalTabRelativeTo, + PositionalTabLeader, +} from 'docx'; +import dayjs from 'dayjs'; +import { cloneDeep, groupBy, isEmpty, isNotEmpty, sortBy, 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 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, }; + }); + // console.log('\ndataRollSS', dataRollSS); + // console.log('\n\n', dataList[0].info.product_type_id, dataRollSS); + + // 人等分组只取平季, 因为产品只一行 + 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; + // console.log('allQuotesSSS', allQuotesSS, '\n', allQuotesPS, '\n', allQuotesSSS); + + // const groupSizeSS = (allQuotesSS).reduce((aq, cq) => aq.concat([[cq.group_size_min, cq.group_size_max]]), []); + 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]); + return aq; + }, {}); + // console.log('PGroupSizeSS', structuredClone(PGroupSizeSS)); + const _SSMinSet = uniqWith(Object.values(PGroupSizeSS), (a, b) => a.join(',') === b.join(',')); + // console.log('SSMinSet', _SSMinSet); + const uSSsizeSetArr = uniqueBySub(_SSMinSet); + // console.log('uSSsizeSetArr', uSSsizeSetArr); + 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 aaa', PGroupSizeSS); + 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 ? Infinity : Number(minsArr[idx + 1])-1; + acc.push([Number(curr), _max]); + return acc; + }, [])); + return arrSets; + }); + // console.log('PGroupSizeSS fff', SSsizeSets); + const compactSizeSets = { SSsizeSetKey: uSSsizeSetArr.map((s) => s.join(',')).filter(isNotEmpty), sizeSets: SSsizeSets }; + // console.log(compactSizeSets); + const chunkSS = structuredClone(dataRollSS).map(rowp => { + // console.log(rowp.info.id, rowp.info.product_title); + + const pkey = (PGroupSizeSS[rowp.info.id] || []).join(',') || compactSizeSets.SSsizeSetKey[0]; // todo: + // console.log('pkey', pkey); + + const thisRange = (PGroupSizeSS[rowp.info.id] || []).reduce((acc, curr, idx, minsArr) => { + const _max = idx === minsArr.length - 1 ? Infinity : 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(); + // if (isEmpty(ssSets)) { + // console.group('quotation item'); + // console.log(rowp.info.id, rowp.info.product_title, rowp); + // console.log(ssSets); + // console.log([quoteItem.group_size_min, quoteItem.group_size_max]); + // console.groupEnd(); + // } + // console.group('quotation item'); + // console.log(ssSets); + // console.log([quoteItem.group_size_min, quoteItem.group_size_max]); + 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; + // console.log('find mmm', matchRange, findEnd, findStart, finalRange); + // console.log('matchRange ', 'find', [quoteItem.group_size_min, quoteItem.group_size_max], 'matched', finalRange); + // console.groupEnd(); + 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, + } + }); + // console.log('quotation', chunkSS); + 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 DOC_TITLE = '地接合同'; +const pageMargins = { + top: `15mm`, + bottom: `15mm`, + left: `10mm`, + right: `10mm`, +}; +const tableBorderNone = { + top: { style: BorderStyle.NONE, size: 0, color: 'FFFFFF' }, + bottom: { style: BorderStyle.NONE, size: 0, color: 'FFFFFF' }, + left: { style: BorderStyle.NONE, size: 0, color: 'FFFFFF' }, + right: { style: BorderStyle.NONE, size: 0, color: 'FFFFFF' }, +}; +const tableBorderOne = { + top: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, +}; + +export default class AgencyContract { + 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); + // console.log('*********************************************************************************************'); + + const document = new Document({ + creator: 'China Highlights', + title: h1, + subject: '2025桂林海纳价格合同模板', + // description: '', + styles: { + paragraphStyles: [ + { + id: 'Normal', + name: 'Normal', + quickFormat: true, + run: { + size: 22, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + 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, + }, + }, + }, + { + id: 'Heading1', + name: 'Heading 1', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 32, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 200, + after: 200, + }, + }, + }, + { + id: 'Heading2', + name: 'Heading 2', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 28, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 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: [ + { + properties: { + page: { + pageNumbers: { + start: 1, + formatType: NumberFormat.DECIMAL, + }, + margin: pageMargins, + }, + }, + headers: { + default: new Header({ + children: [ + this.createPageHeaderText(`Email: xeniayou@hainatravel.com`, { italics: false }), + this.createPageHeaderText(`https://www.chinahighlights.com`, { italics: false }), + ], + }), + }, + footers: { + default: new Footer({ + children: [ + new Paragraph({ + alignment: AlignmentType.CENTER, + children: [ + new TextRun({ + children: ['第', PageNumber.CURRENT, '页'], + size: 20, + }), + new TextRun({ + children: [', 共 ', PageNumber.TOTAL_PAGES, '页'], + size: 20, + }), + ], + }), + new Paragraph({ + alignment: AlignmentType.RIGHT, + children: [ + new TextRun({ + text: `${new Date().toLocaleString()}系统生成`, + italics: true, + size: 20, + }), + ], + }), + ], + }), + }, + children: [ + this.createTitle(h1), + this.createSubHeading(`价格有效期: ${yearStart}-${yearEnd}`, { alignment: AlignmentType.CENTER }), + + ...(isEmpty(agencyProducts['6']) + ? [] + : [this.createSubHeading(`综费`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_6Q(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_JD(use_year, agencyProducts['J'])]), + ...(isEmpty(agencyProducts['Q']) + ? [] + : [this.createSubHeading(`导游`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_6Q(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_JD(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(), + ], + }, + ], + }); + + return document; + } + + /** + * 综费 + * 导游 + */ + createTable_6Q(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) { + // console.log('*********************************************************************************************'); + const { chunk, 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, + }), + ], + }); + const tableS = sizeSets.reduce((acc, size) => { + const subTable = 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: size.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: '' })] }), + ...size.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: `${ss[0]}` })] })), + ], + }), + ...chunk + .filter((p) => p.sizeSetsSS === size.map((mr) => mr[0]).join(',')) + .map((row, ri) => this.createDetailRowSize(size, row, ri, { infoCol })) + .reduce((a, c) => a.concat(c), []), + + this.createTable_Memo(2 + size.length), + ], + }); + acc.push(subTable); + acc.push(new Paragraph({ text: `` })); + return acc; + }, []); + return tableS; + } + + /** + * 车费 + * 包价线路 + */ + createTable_JD(use_year, dataList) { + const { dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season', 'quote_size']); + + const tableHeader = this.createTableHeader; + const tableS = sizeSets.reduce((rt, setItem) => { + const subTable = new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + tableHeader('项目', 20), + tableHeader(['平季', ...SSRange], 40, { columnSpan: setItem.length }), + tableHeader(['旺季', ...PSRange], 40, { columnSpan: setItem.length }), + ], + }), + new TableRow({ + children: [ + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ...setItem.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: `${ss[0]}` })] })), + ...setItem.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: `${ss[0]}` })] })), + ], + }), + ...dataSource.filter((p) => p.sizeSetsSS === setItem.map((mr) => mr[0]).join(',')) + .map((row, ri) => this.createDetailRowSize(setItem, row, ri, { withSeason: true })).reduce((a, c) => a.concat(c), []), + + this.createTable_Memo(1 + setItem.length * 2), + ], + }); + rt.push(subTable); + rt.push(new Paragraph({ text: `` })); + return rt; + }, []); + return tableS; + } + /** + * 导游 + * @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: '' }) + ], + }); + + const tableS = sizeSets.reduce((acc, size) => { + const subTable = new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + this.createTableHeader('社会餐厅午餐/晚餐', 25), + this.createTableHeader(['价格', ...SSRange, ...PSRange], 50, { columnSpan: size.length }), + this.createTableHeader('司陪餐补', 25), + ], + }), + new TableRow({ + children: [ + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ...size.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: `${ss[0]}` })] })), + // 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.filter((p) => p.sizeSetsSS === size.map((mr) => mr[0]).join(',')) + .map((row, ri) => this.createDetailRowSize(size, row, ri, { withSize: false, extraCol })).reduce((a, c) => a.concat(c), []), + + this.createTable_Memo(1 + size.length + 1), + ], + }); + acc.push(subTable); + acc.push(new Paragraph({ text: `` })); + return acc; + }, []); + return tableS; + } + /** + * 包价线路 + * @deprecated + */ + createTable_D(use_year, dataList) {} + + 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, + alignment: AlignmentType.CENTER, + }); + } + + createTitle(text) { + return new Paragraph({ + text: text, + heading: HeadingLevel.TITLE, + // thematicBreak: true, + alignment: AlignmentType.CENTER, + }); + } + + createSubHeading(text, style = {}) { + return new Paragraph({ + text: text, + heading: HeadingLevel.HEADING_1, + ...style, + }); + } + createPageHeaderText(text, style = {}) { + return new Paragraph({ + alignment: AlignmentType.RIGHT, + children: [ + new TextRun({ + text: text, + italics: true, + size: 20, + ...style, + }), + ], + }); + } +}