|
| 1 | +import type { DocumentSchema, MaterialNode } from '@easyink/schema' |
| 2 | +import { SCHEMA_VERSION } from '@easyink/shared' |
| 3 | + |
| 4 | +const TAX_RED = '#8b1e1e' |
| 5 | +const BLACK = '#111111' |
| 6 | + |
| 7 | +type TextAlign = 'left' | 'center' | 'right' |
| 8 | +type VerticalAlign = 'top' | 'middle' | 'bottom' |
| 9 | + |
| 10 | +interface TextOptions { |
| 11 | + fontSize?: number |
| 12 | + fontWeight?: string |
| 13 | + color?: string |
| 14 | + textAlign?: TextAlign |
| 15 | + verticalAlign?: VerticalAlign |
| 16 | + lineHeight?: number |
| 17 | + writingMode?: 'horizontal' | 'vertical' |
| 18 | +} |
| 19 | + |
| 20 | +function textNode( |
| 21 | + id: string, |
| 22 | + x: number, |
| 23 | + y: number, |
| 24 | + width: number, |
| 25 | + height: number, |
| 26 | + content: string, |
| 27 | + options: TextOptions = {}, |
| 28 | +): MaterialNode { |
| 29 | + return { |
| 30 | + id, |
| 31 | + type: 'text', |
| 32 | + x, |
| 33 | + y, |
| 34 | + width, |
| 35 | + height, |
| 36 | + props: { |
| 37 | + content, |
| 38 | + fontSize: options.fontSize ?? 4, |
| 39 | + fontWeight: options.fontWeight ?? 'normal', |
| 40 | + color: options.color ?? TAX_RED, |
| 41 | + textAlign: options.textAlign ?? 'center', |
| 42 | + verticalAlign: options.verticalAlign ?? 'middle', |
| 43 | + lineHeight: options.lineHeight ?? 1.35, |
| 44 | + writingMode: options.writingMode ?? 'horizontal', |
| 45 | + letterSpacing: 0, |
| 46 | + wrapMode: 'anywhere', |
| 47 | + overflow: 'hidden', |
| 48 | + }, |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +function rectNode( |
| 53 | + id: string, |
| 54 | + x: number, |
| 55 | + y: number, |
| 56 | + width: number, |
| 57 | + height: number, |
| 58 | + borderColor: string, |
| 59 | + borderWidth = 0.25, |
| 60 | +): MaterialNode { |
| 61 | + return { |
| 62 | + id, |
| 63 | + type: 'rect', |
| 64 | + x, |
| 65 | + y, |
| 66 | + width, |
| 67 | + height, |
| 68 | + props: { |
| 69 | + fillColor: 'transparent', |
| 70 | + borderWidth, |
| 71 | + borderColor, |
| 72 | + borderType: 'solid', |
| 73 | + borderRadius: 0, |
| 74 | + }, |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +function ruleNode( |
| 79 | + id: string, |
| 80 | + x: number, |
| 81 | + y: number, |
| 82 | + width: number, |
| 83 | + height: number, |
| 84 | + color = TAX_RED, |
| 85 | +): MaterialNode { |
| 86 | + return { |
| 87 | + id, |
| 88 | + type: 'rect', |
| 89 | + x, |
| 90 | + y, |
| 91 | + width, |
| 92 | + height, |
| 93 | + props: { |
| 94 | + fillColor: color, |
| 95 | + borderWidth: 0, |
| 96 | + borderColor: 'transparent', |
| 97 | + borderType: 'solid', |
| 98 | + borderRadius: 0, |
| 99 | + }, |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +const invoiceElements: MaterialNode[] = [ |
| 104 | + rectNode('vat_outer_frame', 8, 8, 281, 194, BLACK, 0.3), |
| 105 | + |
| 106 | + rectNode('vat_qr_box', 17, 16, 27, 27, TAX_RED, 0.35), |
| 107 | + textNode('vat_qr_placeholder', 19, 24, 23, 12, '动态\n二维码', { |
| 108 | + fontSize: 4.1, |
| 109 | + lineHeight: 1.25, |
| 110 | + }), |
| 111 | + |
| 112 | + textNode('vat_title', 94, 19, 108, 12, '电子发票(普通发票)', { |
| 113 | + fontSize: 8.9, |
| 114 | + fontWeight: 'bold', |
| 115 | + color: TAX_RED, |
| 116 | + }), |
| 117 | + ruleNode('vat_title_rule_1', 100, 34.6, 97, 0.35), |
| 118 | + ruleNode('vat_title_rule_2', 100, 36.2, 97, 0.35), |
| 119 | + |
| 120 | + textNode('vat_number_label', 216, 21, 54, 7, '发票号码:', { |
| 121 | + fontSize: 4.2, |
| 122 | + textAlign: 'left', |
| 123 | + }), |
| 124 | + textNode('vat_date_label', 216, 34, 54, 7, '开票日期:', { |
| 125 | + fontSize: 4.2, |
| 126 | + textAlign: 'left', |
| 127 | + }), |
| 128 | + |
| 129 | + rectNode('vat_main_frame', 15, 50, 267, 130, TAX_RED, 0.3), |
| 130 | + |
| 131 | + ruleNode('vat_buyer_label_split', 23, 50, 0.3, 31), |
| 132 | + ruleNode('vat_party_split', 149, 50, 0.3, 31), |
| 133 | + ruleNode('vat_seller_label_split', 157, 50, 0.3, 31), |
| 134 | + ruleNode('vat_party_bottom', 15, 81, 267, 0.3), |
| 135 | + |
| 136 | + textNode('vat_buyer_label', 15.5, 53, 7, 24, '购买方信息', { |
| 137 | + fontSize: 4.1, |
| 138 | + writingMode: 'vertical', |
| 139 | + }), |
| 140 | + textNode('vat_buyer_fields', 25, 58, 116, 14, '名称:\n统一社会信用代码/纳税人识别号:', { |
| 141 | + fontSize: 4.2, |
| 142 | + lineHeight: 1.55, |
| 143 | + textAlign: 'left', |
| 144 | + }), |
| 145 | + textNode('vat_seller_label', 149.5, 53, 7, 24, '销售方信息', { |
| 146 | + fontSize: 4.1, |
| 147 | + writingMode: 'vertical', |
| 148 | + }), |
| 149 | + textNode('vat_seller_fields', 159, 58, 116, 14, '名称:\n统一社会信用代码/纳税人识别号:', { |
| 150 | + fontSize: 4.2, |
| 151 | + lineHeight: 1.55, |
| 152 | + textAlign: 'left', |
| 153 | + }), |
| 154 | + |
| 155 | + textNode('vat_item_name_header', 34, 82, 52, 9, '项目名称', { fontSize: 4.2 }), |
| 156 | + textNode('vat_spec_header', 91, 82, 32, 9, '规格型号', { fontSize: 4.2 }), |
| 157 | + textNode('vat_unit_header', 122, 82, 18, 9, '单位', { fontSize: 4.2 }), |
| 158 | + textNode('vat_qty_header', 142, 82, 23, 9, '数量', { fontSize: 4.2 }), |
| 159 | + textNode('vat_price_header', 168, 82, 25, 9, '单价', { fontSize: 4.2 }), |
| 160 | + textNode('vat_amount_header', 204, 82, 25, 9, '金额', { fontSize: 4.2 }), |
| 161 | + textNode('vat_tax_rate_header', 235, 82, 31, 9, '税率/征收率', { fontSize: 4.2 }), |
| 162 | + textNode('vat_tax_amount_header', 264, 82, 16, 9, '税额', { fontSize: 4.2 }), |
| 163 | + |
| 164 | + ruleNode('vat_items_bottom', 15, 143.5, 267, 0.3), |
| 165 | + textNode('vat_total_label', 35, 135.8, 38, 8, '合 计', { fontSize: 4.2 }), |
| 166 | + |
| 167 | + ruleNode('vat_total_amount_bottom', 15, 154.5, 267, 0.3), |
| 168 | + ruleNode('vat_total_amount_label_split', 84, 143.5, 0.3, 11), |
| 169 | + textNode('vat_amount_words_label', 34, 145.4, 48, 7, '价税合计(大写)', { |
| 170 | + fontSize: 4.1, |
| 171 | + }), |
| 172 | + textNode('vat_amount_number_label', 201, 145.4, 60, 7, '(小写)', { |
| 173 | + fontSize: 4.1, |
| 174 | + }), |
| 175 | + |
| 176 | + ruleNode('vat_remark_top', 15, 165.5, 267, 0.3), |
| 177 | + ruleNode('vat_remark_label_split', 23, 165.5, 0.3, 14.5), |
| 178 | + textNode('vat_remark_label', 15.5, 168, 7, 9.5, '备注', { |
| 179 | + fontSize: 4.1, |
| 180 | + writingMode: 'vertical', |
| 181 | + }), |
| 182 | + |
| 183 | + textNode('vat_issuer_label', 36, 187, 38, 7, '开票人:', { |
| 184 | + fontSize: 4.2, |
| 185 | + textAlign: 'left', |
| 186 | + }), |
| 187 | +] |
| 188 | + |
| 189 | +/** |
| 190 | + * 增值税电子普通发票空白模板。 |
| 191 | + */ |
| 192 | +export const vatElectronicInvoiceTemplate: DocumentSchema = { |
| 193 | + version: SCHEMA_VERSION, |
| 194 | + unit: 'mm', |
| 195 | + meta: { |
| 196 | + name: '增值税电子普通发票', |
| 197 | + description: 'A4 横向增值税发票(电子/普通发票)空白版式。', |
| 198 | + }, |
| 199 | + page: { |
| 200 | + mode: 'fixed', |
| 201 | + width: 297, |
| 202 | + height: 210, |
| 203 | + background: { |
| 204 | + color: '#ffffff', |
| 205 | + }, |
| 206 | + print: { |
| 207 | + orientation: 'landscape', |
| 208 | + }, |
| 209 | + }, |
| 210 | + guides: { x: [], y: [] }, |
| 211 | + elements: invoiceElements, |
| 212 | +} |
0 commit comments