@things-factory/tax-invoice 8.0.0-beta.9 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/client/bootstrap.ts +1 -0
  2. package/client/index.ts +0 -0
  3. package/client/pages/main.ts +25 -0
  4. package/client/pages/tax-audit-result/tax-audit-result-importer.ts +87 -0
  5. package/client/pages/tax-audit-result/tax-audit-result-list-page.ts +331 -0
  6. package/client/pages/tax-invoice/tax-invoice-detail-popup.ts +454 -0
  7. package/client/pages/tax-invoice/tax-invoice-list-page.ts +525 -0
  8. package/client/pages/tax-invoice-api/tax-invoice-api-page.ts +418 -0
  9. package/client/pages/tax-invoice-company/tax-invoice-company-list-page.ts +447 -0
  10. package/client/pages/tax-invoice-send/company-list-popup.ts +188 -0
  11. package/client/pages/tax-invoice-send/tax-invoice-send-page.ts +196 -0
  12. package/client/pages/tax-invoice-send/tax-invoice-send.ts +1181 -0
  13. package/client/route.ts +23 -0
  14. package/client/tsconfig.json +11 -0
  15. package/dist-client/tsconfig.tsbuildinfo +1 -1
  16. package/dist-server/tsconfig.tsbuildinfo +1 -1
  17. package/package.json +8 -8
  18. package/server/index.ts +2 -0
  19. package/server/routes.ts +28 -0
  20. package/server/service/index.ts +37 -0
  21. package/server/service/tax-audit-result/event-subscriber.ts +17 -0
  22. package/server/service/tax-audit-result/index.ts +9 -0
  23. package/server/service/tax-audit-result/tax-audit-result-history.ts +120 -0
  24. package/server/service/tax-audit-result/tax-audit-result-mutation.ts +198 -0
  25. package/server/service/tax-audit-result/tax-audit-result-query.ts +65 -0
  26. package/server/service/tax-audit-result/tax-audit-result-type.ts +61 -0
  27. package/server/service/tax-audit-result/tax-audit-result.ts +99 -0
  28. package/server/service/tax-invoice/index.ts +6 -0
  29. package/server/service/tax-invoice/tax-invoice-mutation.ts +138 -0
  30. package/server/service/tax-invoice/tax-invoice-query.ts +99 -0
  31. package/server/service/tax-invoice/tax-invoice-type.ts +236 -0
  32. package/server/service/tax-invoice/tax-invoice-util.ts +224 -0
  33. package/server/service/tax-invoice/tax-invoice.ts +393 -0
  34. package/server/service/tax-invoice-api/index.ts +6 -0
  35. package/server/service/tax-invoice-api/tax-invoice-api-mutation.ts +275 -0
  36. package/server/service/tax-invoice-api/tax-invoice-api-query.ts +40 -0
  37. package/server/service/tax-invoice-api/tax-invoice-api-type.ts +83 -0
  38. package/server/service/tax-invoice-api/tax-invoice-api.ts +128 -0
  39. package/server/service/tax-invoice-company/index.ts +6 -0
  40. package/server/service/tax-invoice-company/tax-invoice-company-mutation.ts +84 -0
  41. package/server/service/tax-invoice-company/tax-invoice-company-query.ts +86 -0
  42. package/server/service/tax-invoice-company/tax-invoice-company-type.ts +105 -0
  43. package/server/service/tax-invoice-company/tax-invoice-company.ts +128 -0
  44. package/server/service/tax-invoice-item/index.ts +6 -0
  45. package/server/service/tax-invoice-item/tax-invoice-item-mutation.ts +128 -0
  46. package/server/service/tax-invoice-item/tax-invoice-item-query.ts +60 -0
  47. package/server/service/tax-invoice-item/tax-invoice-item-type.ts +77 -0
  48. package/server/service/tax-invoice-item/tax-invoice-item.ts +95 -0
  49. package/server/service/unipost-api/tax-invoice-inquiry.ts +99 -0
  50. package/server/service/unipost-api/tax-invoice-send.ts +32 -0
  51. package/server/tsconfig.json +10 -0
@@ -0,0 +1,1181 @@
1
+ import '@operato/data-grist'
2
+
3
+ import { css, html, LitElement } from 'lit'
4
+ import { CustomAlert } from '@operato/shell'
5
+ import { CommonGristStyles, ScrollbarStyles } from '@operato/styles'
6
+ import { property, customElement } from 'lit/decorators.js'
7
+ import { client } from '@operato/graphql'
8
+ import { i18next, localize } from '@operato/i18n'
9
+ import gql from 'graphql-tag'
10
+ import {
11
+ BUSINESS_REGISTRATION_CATEGORY,
12
+ BusinessRegistrationCategory,
13
+ Category,
14
+ INVOICE_TYPE,
15
+ InvoiceType,
16
+ PURPOSE,
17
+ Purpose
18
+ } from '../tax-invoice/tax-invoice-list-page'
19
+ import { openPopup } from '@operato/layout'
20
+
21
+ export const SHORT_INVOICE_TYPE = {
22
+ TAX_INVOICE: {
23
+ [InvoiceType.TAX_INVOICE]: '일반',
24
+ [InvoiceType.SMALL_BUSINESS_TAX_INVOICE]: '영세율',
25
+ [InvoiceType.CONSIGNMENT_TAX_INVOICE]: '위수탁',
26
+ [InvoiceType.SMALL_BUSINESS_CONSIGNMENT_TAX_INVOICE]: '위수탁영세율'
27
+ },
28
+ INVOICE: {
29
+ [InvoiceType.INVOICE]: '일반',
30
+ [InvoiceType.CONSIGNMENT_INVOICE]: '위수탁'
31
+ // [InvoiceType.IMPORT_INVOICE]: INVOICE_TYPE[IMPORT_INVOICE],
32
+ }
33
+ }
34
+
35
+ export const SHORT_BUSINESS_REGISTRATION_CATEGORY = {
36
+ [BusinessRegistrationCategory.BUSINESS_REGISTRATION_NUMBER]: '사업자',
37
+ [BusinessRegistrationCategory.RESIDENT_REGISTRATION_NUMBER]: '개인',
38
+ [BusinessRegistrationCategory.FOREIGNER_REGISTRATION_NUMBER]: '외국인'
39
+ }
40
+
41
+ export interface Supplier {
42
+ supplierBusinessRegistrationCategory: string
43
+ supplierBusinessRegistrationNumber: string
44
+ supplierBranchIdentification?: string
45
+ supplierBusinessName: string
46
+ supplierRepresentativeName: string
47
+ supplierAddress?: string
48
+ supplierBusinessType?: string
49
+ supplierIndustryType?: string
50
+ supplierDepartment?: string
51
+ supplierContactName?: string
52
+ supplierContactPhone?: string
53
+ supplierContactEmail?: string
54
+ supplierContactCellPhone?: string
55
+ }
56
+ export interface Buyer {
57
+ buyerBusinessRegistrationCategory: BusinessRegistrationCategory
58
+ buyerBusinessRegistrationNumber: string
59
+ buyerBranchIdentification?: string
60
+ buyerBusinessName: string
61
+ buyerRepresentativeName: string
62
+ buyerAddress?: string
63
+ buyerBusinessType?: string
64
+ buyerIndustryType?: string
65
+ buyer1stDepartment?: string
66
+ buyer1stContactName?: string
67
+ buyer1stContactPhone?: string
68
+ buyer1stContactEmail?: string
69
+ buyer1stContactCellPhone?: string
70
+ buyer2ndDepartment?: string
71
+ buyer2ndContactName?: string
72
+ buyer2ndContactPhone?: string
73
+ buyer2ndContactEmail?: string
74
+ buyer2ndContactCellPhone?: string
75
+ }
76
+ export interface TaxInvoice {
77
+ category: Category
78
+ issueNo?: string
79
+ approvalNo?: string
80
+ originalApprovalNo?: string
81
+ issueDate: string
82
+ signatureDate?: string
83
+ invoiceType: InvoiceType
84
+ purpose: Purpose
85
+ remark0?: string
86
+ remark1?: string
87
+ remark2?: string
88
+ paymentAmountCash?: number
89
+ paymentAmountCheck?: number
90
+ paymentAmountPromissoryNote?: number
91
+ paymentAmountAccountsReceivable?: number
92
+ supplyAmount: number
93
+ taxAmount: number
94
+ totalAmount: number
95
+ // revisedReason?: RevisedReason
96
+ }
97
+ export interface TaxInvoiceItem {
98
+ sequence: number
99
+ supplyDate: string
100
+ itemName: string
101
+ specification: string
102
+ quantity: number
103
+ unitPrice: number
104
+ supplyPrice: number
105
+ taxPrice: number
106
+ remark?: string
107
+ }
108
+
109
+ @customElement('tax-invoice-send')
110
+ export class TaxInvoiceSend extends localize(i18next)(LitElement) {
111
+ static styles = [
112
+ ScrollbarStyles,
113
+ CommonGristStyles,
114
+ css`
115
+ :host {
116
+ display: flex;
117
+ flex-direction: column;
118
+ overflow-y: auto;
119
+ overflow-x: hidden;
120
+
121
+ width: 100%;
122
+ min-width: 850px;
123
+ padding: 0 40px 40px 40px;
124
+
125
+ --grid-record-emphasized-background-color: red;
126
+ --grid-record-emphasized-color: yellow;
127
+ }
128
+
129
+ .flex-row {
130
+ display: flex;
131
+ flex-direction: row;
132
+ }
133
+ .flex-column {
134
+ display: flex;
135
+ flex-direction: column;
136
+ }
137
+
138
+ input[type='text'],
139
+ input[type='date'],
140
+ input[type='number'] {
141
+ width: 85%;
142
+ max-width: calc(100% - 12px);
143
+ height: 14px;
144
+ margin: 0;
145
+ padding: 4px 5px 5px;
146
+ line-height: 14px;
147
+ border: 1px solid #bbb;
148
+ background: #fff;
149
+ letter-spacing: 0.5px;
150
+ }
151
+ input[type='date'] {
152
+ width: auto;
153
+ }
154
+ input[readonly],
155
+ input:disabled {
156
+ background-color: #eee;
157
+ }
158
+
159
+ div.invoice-type {
160
+ font-size: 18px;
161
+ margin-top: 15px;
162
+ }
163
+
164
+ table {
165
+ border: solid;
166
+ border-width: 2px 0;
167
+ border-collapse: collapse;
168
+ border-spacing: 0;
169
+ }
170
+
171
+ .title table {
172
+ border-color: #d3dbe5;
173
+ width: 100%;
174
+ height: 33px;
175
+ margin-top: 10px;
176
+ font-size: 12px;
177
+
178
+ th {
179
+ font-size: 13px;
180
+ background-color: #e9edf6;
181
+ min-width: 140px;
182
+ }
183
+ td {
184
+ width: 50%;
185
+ padding-left: 10px;
186
+
187
+ & > * {
188
+ vertical-align: middle;
189
+ }
190
+ & > input {
191
+ margin: 0 5px 0 1px;
192
+ }
193
+ }
194
+ }
195
+
196
+ div.body {
197
+ margin-top: 15px;
198
+ font-size: 14px;
199
+
200
+ div.data {
201
+ display: flex;
202
+ margin-top: 5px;
203
+
204
+ table {
205
+ flex: 1;
206
+
207
+ td,
208
+ th {
209
+ padding: 5px;
210
+ min-width: 60px;
211
+ }
212
+ td {
213
+ padding-left: 8px;
214
+ }
215
+ th.target {
216
+ width: 25px;
217
+ writing-mode: vertical-rl;
218
+ min-width: unset;
219
+ }
220
+
221
+ &.supplier {
222
+ border-color: #f64843;
223
+
224
+ td,
225
+ th {
226
+ background-color: #fef5f7;
227
+ border: solid 1.5px #f5b1b1;
228
+ }
229
+ th.target {
230
+ background-color: #fedbde;
231
+ }
232
+ }
233
+ &.buyer {
234
+ border-color: #007ed9;
235
+
236
+ td,
237
+ th {
238
+ background-color: #f7f7ff;
239
+ border: solid 1.5px #a7bcff;
240
+ }
241
+ th.target {
242
+ background-color: #c6daf6;
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ div.header {
249
+ display: flex;
250
+
251
+ span {
252
+ flex: 1;
253
+ }
254
+ }
255
+
256
+ div.info {
257
+ margin-top: 15px;
258
+ border: solid;
259
+ border-color: #000;
260
+ border-width: 2px 0px 0px 0px;
261
+
262
+ div.flex-row div {
263
+ border: solid #c4d2ff;
264
+ border-width: 0px 1.5px 1.5px 1.5px;
265
+ margin-right: -1px;
266
+ padding: 5px;
267
+ text-align: center;
268
+
269
+ &.title {
270
+ background-color: #e9edf5;
271
+ font-weight: bold;
272
+ flex: 1;
273
+ }
274
+ &.value {
275
+ flex: 3;
276
+ text-align: left;
277
+
278
+ .desc {
279
+ font-size: 11px;
280
+ color: #c90000;
281
+ }
282
+ }
283
+ }
284
+ }
285
+
286
+ div.items,
287
+ div.summary {
288
+ margin-top: 20px;
289
+
290
+ table {
291
+ width: 100%;
292
+ border-color: #000;
293
+ border-width: 2px 0px 0px 0px;
294
+
295
+ th {
296
+ background-color: #e9edf5;
297
+ }
298
+ th,
299
+ td {
300
+ border: solid 1.5px #c4d2ff;
301
+ padding: 5px;
302
+ text-align: center;
303
+ }
304
+ }
305
+ }
306
+
307
+ div.summary {
308
+ display: flex;
309
+
310
+ & > table {
311
+ flex: 6.7;
312
+ }
313
+ & > div {
314
+ flex: 3.2;
315
+ display: flex;
316
+ justify-content: center;
317
+ align-items: center;
318
+ border: 1.5px solid #c4d2ff;
319
+ border-top: 2px solid #000;
320
+ }
321
+
322
+ span.type-wrapper {
323
+ margin: 0 10px;
324
+
325
+ & > div {
326
+ display: flex;
327
+ justify-content: center;
328
+ align-items: center;
329
+ gap: 4px;
330
+
331
+ input[type='radio' i] {
332
+ margin: 0px;
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ div.items {
339
+ div.desc {
340
+ font-weight: 400;
341
+ font-size: 12px;
342
+ margin-bottom: 5px;
343
+ }
344
+ div.desc-date {
345
+ color: #005d98;
346
+ }
347
+ button {
348
+ font-size: 13px;
349
+ height: 25px;
350
+ cursor: pointer;
351
+ text-align: center;
352
+ font-weight: 600;
353
+ border-radius: 2px;
354
+ border-color: #ccc;
355
+ }
356
+ .btn-add {
357
+ color: #333;
358
+ background-color: #f9f9f9;
359
+ }
360
+ .btn-delete {
361
+ color: #fff;
362
+ background-color: #5c636e;
363
+ border: none;
364
+ }
365
+ }
366
+
367
+ *[required]::before {
368
+ content: '*';
369
+ color: red;
370
+ margin-right: 2px;
371
+ }
372
+ }
373
+ `
374
+ ]
375
+
376
+ // 오늘 날짜 YYYY-MM-DD 형식으로 뽑기
377
+ private today: string = new Intl.DateTimeFormat('fr-CA', { timeZone: 'Asia/Seoul' }).format(new Date())
378
+ private isTaxFree = false
379
+ private defaultSupplier = {
380
+ supplierBusinessRegistrationCategory: '01',
381
+ supplierBusinessRegistrationNumber: '',
382
+ supplierBranchIdentification: '',
383
+ supplierBusinessName: '',
384
+ supplierRepresentativeName: ''
385
+ }
386
+ private defaultBuyer = {
387
+ buyerBusinessRegistrationCategory: BusinessRegistrationCategory.BUSINESS_REGISTRATION_NUMBER,
388
+ buyerBusinessRegistrationNumber: '',
389
+ buyerBusinessName: '',
390
+ buyerRepresentativeName: ''
391
+ }
392
+ private defaultTaxInvoice = {
393
+ invoiceType: InvoiceType.TAX_INVOICE,
394
+ category: Category.EXPORT,
395
+ issueDate: this.today,
396
+ purpose: Purpose.INVOICE,
397
+ supplyAmount: 0,
398
+ taxAmount: 0,
399
+ totalAmount: 0
400
+ }
401
+ private defaultTaxInvoiceItem = {
402
+ sequence: new Date().getTime(), // 구분용 임시 값 (중복되면 안되니 생성 마다 넣어주기)
403
+ supplyDate: '',
404
+ itemName: '',
405
+ specification: '',
406
+ quantity: 0,
407
+ unitPrice: 0,
408
+ supplyPrice: 0,
409
+ taxPrice: 0
410
+ }
411
+
412
+ @property({ type: Object }) supplier: Supplier = this.defaultSupplier
413
+ @property({ type: Object }) buyer: Buyer = this.defaultBuyer
414
+ @property({ type: Object }) taxInvoice: TaxInvoice = this.defaultTaxInvoice
415
+ @property({ type: Array }) taxInvoiceItems: TaxInvoiceItem[] = [{ ...this.defaultTaxInvoiceItem }]
416
+
417
+ render() {
418
+ return html`
419
+ <div class="invoice-type">
420
+ * ${INVOICE_TYPE[this.taxInvoice?.invoiceType]}
421
+ (${SHORT_BUSINESS_REGISTRATION_CATEGORY[this.buyer.buyerBusinessRegistrationCategory]})
422
+ </div>
423
+
424
+ <div class="title">
425
+ <table>
426
+ <tr>
427
+ <th>${i18next.t('field.short invoice type')}</th>
428
+ <td>
429
+ ${Object.keys(SHORT_INVOICE_TYPE['TAX_INVOICE']).map(key => {
430
+ const disabled: boolean =
431
+ key !== InvoiceType.TAX_INVOICE && key !== InvoiceType.SMALL_BUSINESS_TAX_INVOICE
432
+ const checked: boolean = key === InvoiceType.TAX_INVOICE
433
+ return html`
434
+ <label for="${'tax_' + key}">${SHORT_INVOICE_TYPE['TAX_INVOICE'][key]}</label>
435
+ <input
436
+ type="radio"
437
+ id="${'tax_' + key}"
438
+ .value="${key}"
439
+ name="invoiceType"
440
+ ?checked=${checked}
441
+ ?disabled=${disabled}
442
+ @change=${this._onInputTaxInvoice}
443
+ />
444
+ `
445
+ })}
446
+ </td>
447
+ <th>${i18next.t('field.tax invoice buyer registration category')}</th>
448
+ <td>
449
+ ${Object.keys(BUSINESS_REGISTRATION_CATEGORY).map(key => {
450
+ const disabled: boolean = key !== BusinessRegistrationCategory.BUSINESS_REGISTRATION_NUMBER
451
+ const checked: boolean = key === BusinessRegistrationCategory.BUSINESS_REGISTRATION_NUMBER
452
+ return html`
453
+ <label for="${'tax_' + key}">${BUSINESS_REGISTRATION_CATEGORY[key]}</label>
454
+ <input
455
+ type="radio"
456
+ id="${'tax_' + key}"
457
+ value="${key}"
458
+ name="buyerBusinessRegistrationCategory"
459
+ ?checked=${checked}
460
+ ?disabled=${disabled}
461
+ />
462
+ `
463
+ })}
464
+ </td>
465
+ </tr>
466
+ </table>
467
+ </div>
468
+
469
+ <div class="body">
470
+ <div class="header">
471
+ <span><button @click=${this._openSupplierPopup}>공급처 선택</button></span>
472
+ <span><button @click=${this._openBuyerPopup}>공급받는자 선택</button></span>
473
+ </div>
474
+
475
+ <div class="data">
476
+ <table class="supplier">
477
+ <colgroup>
478
+ <col width="150px" />
479
+ <col width="18%" />
480
+ <col width="32%" />
481
+ <col width="18%" />
482
+ <col width="32%" />
483
+ </colgroup>
484
+ <tr>
485
+ <th rowspan="6" class="target">${i18next.t('field.tax invoice supplier')}</th>
486
+ <th required>${i18next.t('field.short business registration number')}</th>
487
+ <td>
488
+ ${this.supplier?.supplierBusinessRegistrationNumber?.replace(/(\S{3})(\S{2})(\S{5})/g, '$1-$2-$3')}
489
+ </td>
490
+ <th>${i18next.t('field.short branch identification')}</th>
491
+ <td>${this.supplier?.supplierBranchIdentification}</td>
492
+ </tr>
493
+ <tr>
494
+ <th required>${i18next.t('field.short tax invoice business name')}</th>
495
+ <td>
496
+ <input
497
+ type="text"
498
+ name="supplierBusinessName"
499
+ .value=${this.supplier?.supplierBusinessName || ''}
500
+ @input=${this._onInputTaxInvoice}
501
+ />
502
+ </td>
503
+ <th required>${i18next.t('field.tax invoice representative name')}</th>
504
+ <td>
505
+ <input
506
+ type="text"
507
+ name="supplierRepresentativeName"
508
+ .value=${this.supplier?.supplierRepresentativeName || ''}
509
+ @input=${this._onInputTaxInvoice}
510
+ />
511
+ </td>
512
+ </tr>
513
+ <tr>
514
+ <th>${i18next.t('field.address')}</th>
515
+ <td colspan="3">${this.supplier?.supplierAddress}</td>
516
+ </tr>
517
+ <tr>
518
+ <th>${i18next.t('field.business type')}</th>
519
+ <td>
520
+ <input
521
+ type="text"
522
+ name="supplierBusinessType"
523
+ .value=${this.supplier?.supplierBusinessType || ''}
524
+ @input=${this._onInputTaxInvoice}
525
+ />
526
+ </td>
527
+ <th>${i18next.t('field.industry type')}</th>
528
+ <td>
529
+ <input
530
+ type="text"
531
+ name="supplierIndustryType"
532
+ .value=${this.supplier?.supplierIndustryType || ''}
533
+ @input=${this._onInputTaxInvoice}
534
+ />
535
+ </td>
536
+ </tr>
537
+ <tr>
538
+ <th rowspan="2">${i18next.t('field.email')}</th>
539
+ <td rowspan="2" colspan="3">
540
+ <input
541
+ type="text"
542
+ name="supplierContactEmail"
543
+ .value=${this.supplier?.supplierContactEmail || ''}
544
+ @input=${this._onInputTaxInvoice}
545
+ />
546
+ </td>
547
+ </tr>
548
+ <tr></tr>
549
+ </table>
550
+
551
+ <table class="buyer">
552
+ <colgroup>
553
+ <col width="150px" />
554
+ <col width="18%" />
555
+ <col width="32%" />
556
+ <col width="18%" />
557
+ <col width="32%" />
558
+ </colgroup>
559
+ <tr>
560
+ <th rowspan="6" class="target">${i18next.t('field.tax invoice buyer')}</th>
561
+ <th required>${i18next.t('field.short business registration number')}</th>
562
+ <td>
563
+ <input
564
+ type="text"
565
+ maxlength="10"
566
+ name="buyerBusinessRegistrationNumber"
567
+ .value=${this.buyer?.buyerBusinessRegistrationNumber || ''}
568
+ @input=${this._onInputTaxInvoice}
569
+ />
570
+ </td>
571
+ <th>${i18next.t('field.short branch identification')}</th>
572
+ <td>
573
+ <input
574
+ type="text"
575
+ name="buyerBranchIdentification"
576
+ .value=${this.buyer?.buyerBranchIdentification || ''}
577
+ @input=${this._onInputTaxInvoice}
578
+ />
579
+ </td>
580
+ </tr>
581
+ <tr>
582
+ <th required>${i18next.t('field.short tax invoice business name')}</th>
583
+ <td>
584
+ <input
585
+ type="text"
586
+ name="buyerBusinessName"
587
+ .value=${this.buyer?.buyerBusinessName || ''}
588
+ @input=${this._onInputTaxInvoice}
589
+ />
590
+ </td>
591
+ <th required>${i18next.t('field.tax invoice representative name')}</th>
592
+ <td>
593
+ <input
594
+ type="text"
595
+ name="buyerRepresentativeName"
596
+ .value=${this.buyer?.buyerRepresentativeName || ''}
597
+ @input=${this._onInputTaxInvoice}
598
+ />
599
+ </td>
600
+ </tr>
601
+ <tr>
602
+ <th>${i18next.t('field.address')}</th>
603
+ <td colspan="3">
604
+ <input
605
+ type="text"
606
+ name="buyerAddress"
607
+ .value=${this.buyer?.buyerAddress || ''}
608
+ @input=${this._onInputTaxInvoice}
609
+ />
610
+ </td>
611
+ </tr>
612
+ <tr>
613
+ <th>${i18next.t('field.business type')}</th>
614
+ <td>
615
+ <input
616
+ type="text"
617
+ name="buyerBusinessType"
618
+ .value=${this.buyer?.buyerBusinessType || ''}
619
+ @input=${this._onInputTaxInvoice}
620
+ />
621
+ </td>
622
+
623
+ <th>${i18next.t('field.industry type')}</th>
624
+ <td>
625
+ <input
626
+ type="text"
627
+ name="buyerIndustryType"
628
+ .value=${this.buyer?.buyerIndustryType || ''}
629
+ @input=${this._onInputTaxInvoice}
630
+ />
631
+ </td>
632
+ </tr>
633
+ <tr>
634
+ <th>${i18next.t('field.email')}</th>
635
+ <td colspan="3">
636
+ <input
637
+ type="text"
638
+ name="buyer1stContactEmail"
639
+ .value=${this.buyer?.buyer1stContactEmail || ''}
640
+ @input=${this._onInputTaxInvoice}
641
+ />
642
+ </td>
643
+ </tr>
644
+ <tr>
645
+ <th>${i18next.t('field.email')}</th>
646
+ <td colspan="3">
647
+ <input
648
+ type="text"
649
+ name="buyer2ndContactEmail"
650
+ .value=${this.buyer?.buyer2ndContactEmail || ''}
651
+ @input=${this._onInputTaxInvoice}
652
+ />
653
+ </td>
654
+ </tr>
655
+ </table>
656
+ </div>
657
+
658
+ <div class="info flex-column">
659
+ <div class="flex-row">
660
+ <div class="title" required>${i18next.t('field.issue date')}</div>
661
+ <div class="value">
662
+ <input
663
+ type="date"
664
+ name="issueDate"
665
+ .value=${this.taxInvoice.issueDate}
666
+ @input=${this._onInputTaxInvoice}
667
+ />
668
+ <span class="desc">※ 작성일자는 공급 연월일을 의미함</span>
669
+ </div>
670
+ <div class="title">${i18next.t('field.remark0')}</div>
671
+ <div class="value">
672
+ <input
673
+ type="text"
674
+ name="remark0"
675
+ .value=${this.taxInvoice?.remark0 || ''}
676
+ @input=${this._onInputTaxInvoice}
677
+ />
678
+ </div>
679
+ </div>
680
+ <div class="flex-row">
681
+ <div class="title">${i18next.t('field.total amount')}</div>
682
+ <div class="value">${this._numberFormat(this.taxInvoice?.totalAmount)}</div>
683
+ <div class="title">${i18next.t('field.supply amount')}</div>
684
+ <div class="value">${this._numberFormat(this.taxInvoice?.supplyAmount)}</div>
685
+ <div class="title">${i18next.t('field.tax amount')}</div>
686
+ <div class="value">${this._numberFormat(this.taxInvoice?.taxAmount)}</div>
687
+ </div>
688
+ </div>
689
+
690
+ <div class="items">
691
+ <div class="desc desc-date">
692
+ ※ 아래 '품목'의 '월'은 상단 '작성일자'의 '월'이 자동 반영됩니다. 합계의 '계산' 버튼은 금액을 공급가액과
693
+ 세액으로 계산할 수 있습니다.
694
+ </div>
695
+ <div class="desc">
696
+ <button class="btn-add" @click=${this._onAddInvoiceItem}>품목추가</button> 품목은 최대 16개까지 추가, 삭제
697
+ 가능
698
+ </div>
699
+
700
+ <table>
701
+ <colgroup>
702
+ <col width="5%" />
703
+ <col width="5%" />
704
+ <col width="14%" />
705
+ <col width="7%" />
706
+ <col width="7%" />
707
+ <col width="12%" />
708
+ <col width="12%" />
709
+ <col width="12%" />
710
+ <col width="16%" />
711
+ <col width="8%" />
712
+ </colgroup>
713
+ <tr>
714
+ <th>${i18next.t('field.tax invoice month')}</th>
715
+ <th required>${i18next.t('field.tax invoice day')}</th>
716
+ <th required>${i18next.t('field.tax invoice item name')}</th>
717
+ <th>${i18next.t('field.tax invoice ea')}</th>
718
+ <th required>${i18next.t('field.tax invoice quantity')}</th>
719
+ <th required>${i18next.t('field.tax invoice price')}</th>
720
+ <th>${i18next.t('field.supply amount')}</th>
721
+ <th>${i18next.t('field.tax amount')}</th>
722
+ <th>${i18next.t('field.remark0')}</th>
723
+ <th>${i18next.t('field.delete')}</th>
724
+ </tr>
725
+
726
+ ${this.taxInvoiceItems.map(
727
+ (item, idx) => html`
728
+ <tr>
729
+ <input type="hidden" name="sequence" value=${item.sequence} />
730
+ <td>${this.taxInvoice.issueDate.split('-')[1]}</td>
731
+ <td>
732
+ <input
733
+ type="text"
734
+ numeric
735
+ maxlength="2"
736
+ name="supplyDate"
737
+ @input=${e => this._onInputItem(e, idx)}
738
+ />
739
+ </td>
740
+ <td>
741
+ <input
742
+ type="text"
743
+ name="itemName"
744
+ .value=${item.itemName}
745
+ @input=${e => this._onInputItem(e, idx)}
746
+ />
747
+ </td>
748
+ <td>
749
+ <input
750
+ type="text"
751
+ name="specification"
752
+ .value=${item.specification}
753
+ @input=${e => this._onInputItem(e, idx)}
754
+ />
755
+ </td>
756
+ <td>
757
+ <input
758
+ type="text"
759
+ numeric
760
+ name="quantity"
761
+ .value=${this._numberFormat(item.quantity)}
762
+ @input=${e => this._onInputItem(e, idx)}
763
+ />
764
+ </td>
765
+ <td>
766
+ <input
767
+ type="text"
768
+ numeric
769
+ name="unitPrice"
770
+ .value=${this._numberFormat(item.unitPrice)}
771
+ @input=${e => this._onInputItem(e, idx)}
772
+ />
773
+ </td>
774
+ <td>
775
+ <input
776
+ type="text"
777
+ numeric
778
+ name="supplyPrice"
779
+ .value=${this._numberFormat(item.supplyPrice)}
780
+ @input=${e => this._onInputItem(e, idx)}
781
+ />
782
+ </td>
783
+ <td>
784
+ <input
785
+ type="text"
786
+ numeric
787
+ name="taxPrice"
788
+ .value=${this._numberFormat(item.taxPrice)}
789
+ @input=${e => this._onInputItem(e, idx)}
790
+ .disabled=${this.isTaxFree}
791
+ />
792
+ </td>
793
+ <td>
794
+ <input
795
+ type="text"
796
+ name="remark"
797
+ .value=${item.remark || ''}
798
+ @input=${e => this._onInputItem(e, idx)}
799
+ />
800
+ </td>
801
+ <td>
802
+ <button class="btn-delete" @click=${e => this._onRemoveInvoiceItem(e, item.sequence)}>삭제</button>
803
+ </td>
804
+ </tr>
805
+ `
806
+ )}
807
+ </table>
808
+ </div>
809
+
810
+ <div class="summary">
811
+ <table>
812
+ <colgroup>
813
+ <col width="25%" />
814
+ <col width="25%" />
815
+ <col width="25%" />
816
+ <col width="25%" />
817
+ </colgroup>
818
+ <tr>
819
+ <th>${i18next.t('field.tax invoice payment cash')}</th>
820
+ <th>${i18next.t('field.tax invoice payment check')}</th>
821
+ <th>${i18next.t('field.tax invoice payment promissory note')}</th>
822
+ <th>${i18next.t('field.tax invoice payment accounts receivable')}</th>
823
+ </tr>
824
+ <tr>
825
+ <td>
826
+ <input
827
+ type="text"
828
+ numeric
829
+ name="paymentAmountCash"
830
+ .value=${this.taxInvoice?.paymentAmountCash || ''}
831
+ @input=${this._onInputTaxInvoice}
832
+ />
833
+ </td>
834
+ <td>
835
+ <input
836
+ type="text"
837
+ numeric
838
+ name="paymentAmountCheck"
839
+ .value=${this.taxInvoice?.paymentAmountCheck || ''}
840
+ @input=${this._onInputTaxInvoice}
841
+ />
842
+ </td>
843
+ <td>
844
+ <input
845
+ type="text"
846
+ numeric
847
+ name="paymentAmountPromissoryNote"
848
+ .value=${this.taxInvoice?.paymentAmountPromissoryNote || ''}
849
+ @input=${this._onInputTaxInvoice}
850
+ />
851
+ </td>
852
+ <td>
853
+ <input
854
+ type="text"
855
+ numeric
856
+ name="paymentAmountAccountsReceivable"
857
+ .value=${this.taxInvoice?.paymentAmountAccountsReceivable || ''}
858
+ @input=${this._onInputTaxInvoice}
859
+ />
860
+ </td>
861
+ </tr>
862
+ </table>
863
+ <div>
864
+ ${i18next.t('text.prev tax invoice description payment') + ' ('}
865
+ <span class="type-wrapper">
866
+ <div>
867
+ <input
868
+ type="radio"
869
+ id="purpose_20"
870
+ name="purpose"
871
+ .value=${Purpose.INVOICE}
872
+ @change=${this._onInputTaxInvoice}
873
+ checked
874
+ />
875
+ <label for="purpose_20">${PURPOSE[Purpose.INVOICE]}</label>
876
+ </div>
877
+ <div>
878
+ <input
879
+ type="radio"
880
+ id="purpose_10"
881
+ name="purpose"
882
+ .value=${Purpose.RECEIPT}
883
+ @change=${this._onInputTaxInvoice}
884
+ />
885
+ <label for="purpose_10">${PURPOSE[Purpose.RECEIPT]}</label>
886
+ </div>
887
+ </span>
888
+ ${') ' + i18next.t('text.next tax invoice description payment')}
889
+ </div>
890
+ </div>
891
+ </div>
892
+ `
893
+ }
894
+
895
+ async firstUpdated() {
896
+ this._fetchSupplier()
897
+ }
898
+
899
+ async _fetchSupplier() {
900
+ const response = await client.query({
901
+ query: gql`
902
+ query taxInvoiceDefaultCompany {
903
+ response: taxInvoiceDefaultCompany {
904
+ businessRegistrationNumber
905
+ branchIdentification
906
+ businessName
907
+ representativeName
908
+ address
909
+ businessType
910
+ industryType
911
+ department
912
+ contactName
913
+ contactPhone
914
+ contactEmail
915
+ contactCellPhone
916
+ }
917
+ }
918
+ `
919
+ })
920
+
921
+ const supplier = response.data.response
922
+ this.supplier = {
923
+ supplierBusinessRegistrationCategory: supplier.businessRegistrationCategory,
924
+ supplierBusinessRegistrationNumber: supplier.businessRegistrationNumber,
925
+ supplierBranchIdentification: supplier.branchIdentification,
926
+ supplierBusinessName: supplier.businessName,
927
+ supplierRepresentativeName: supplier.representativeName,
928
+ supplierAddress: supplier.address,
929
+ supplierBusinessType: supplier.businessType,
930
+ supplierIndustryType: supplier.industryType,
931
+ supplierDepartment: supplier.department,
932
+ supplierContactName: supplier.contactName,
933
+ supplierContactPhone: supplier.contactPhone,
934
+ supplierContactEmail: supplier.contactEmail,
935
+ supplierContactCellPhone: supplier.contactCellPhone
936
+ }
937
+ }
938
+
939
+ private _numberFormat(value: number): string {
940
+ return !value ? '' : new Intl.NumberFormat('en-US').format(value) || ''
941
+ }
942
+
943
+ // Input 요소의 값이 변경될 때 호출되는 콜백 함수
944
+ private async _onInputTaxInvoice(event: InputEvent) {
945
+ const target = event.target as HTMLInputElement
946
+
947
+ // 숫자 타입은 다른 문자 입력 제거
948
+ if (target.hasAttribute('numeric')) {
949
+ target.value = target.value.replace(/\D/g, '')
950
+ }
951
+
952
+ if (target.name.startsWith('supplier')) {
953
+ this.supplier[target.name] = target.value
954
+ } else if (target.name.startsWith('buyer')) {
955
+ this.buyer[target.name] = target.value
956
+ } else {
957
+ // 날짜 체크가 오늘 날짜를 넘어가면 값 변경 되면 안됨
958
+ if (target.name === 'issueDate' && target.value > this.today) {
959
+ target.value = this.taxInvoice['issueDate']
960
+
961
+ await CustomAlert({
962
+ type: 'warning',
963
+ title: '작성일자는 현재일까지 입력하시기 바랍니다.',
964
+ text: '작성일자는 현재일까지 입력하시기 바랍니다.',
965
+ confirmButton: { text: i18next.t('button.confirm') }
966
+ })
967
+ return
968
+ }
969
+
970
+ // 세금이 없는 영세율이 선택 시 모든 아이템의 세금 0원으로 초기화
971
+ if (target.name === 'invoiceType' && target.value === InvoiceType.SMALL_BUSINESS_TAX_INVOICE) {
972
+ this.isTaxFree = true
973
+ this.taxInvoice = { ...this.taxInvoice, taxAmount: 0 }
974
+ this.taxInvoiceItems = this.taxInvoiceItems.map(item => ({ ...item, taxPrice: 0 }))
975
+ }
976
+
977
+ // 변경시에는 아이템 변경도 리 렌더가 필요해서 데이터 갱신
978
+ this.taxInvoice[target.name] = target.value
979
+ this.taxInvoice = { ...this.taxInvoice }
980
+ }
981
+ }
982
+
983
+ // Input 요소의 값이 변경될 때 호출되는 콜백 함수
984
+ private async _onInputItem(e: InputEvent, idx: number) {
985
+ const target = e.target as HTMLInputElement
986
+ let inputVal: any = target.value
987
+
988
+ // 숫자 타입은 다른 문자 입력 제거
989
+ if (target.hasAttribute('numeric')) {
990
+ inputVal = Number(inputVal.replace(/\D/g, ''))
991
+ target.value = inputVal
992
+ }
993
+
994
+ // 공급일자 케이스 시 => 세금계산서의 작성일자의 YYYY-MM + 아이템의 입력된 DD
995
+ if (target.name === 'supplyDate') {
996
+ const itemDate = this.taxInvoice.issueDate.substring(0, 8) + inputVal.toString().padStart(2, '0')
997
+ if (itemDate > this.taxInvoice.issueDate) {
998
+ await CustomAlert({
999
+ type: 'warning',
1000
+ title: '품목의 일자는 작성일자보다 클 수 없습니다.',
1001
+ text: '품목의 일자는 작성일자보다 클 수 없습니다.',
1002
+ confirmButton: { text: i18next.t('button.confirm') }
1003
+ })
1004
+
1005
+ target.value = ''
1006
+ }
1007
+ inputVal = itemDate
1008
+ }
1009
+
1010
+ // 수량, 단가 수정시 공급가액/세액 자동 계산
1011
+ if (target.name == 'quantity' || target.name == 'unitPrice') {
1012
+ const supplyPrice =
1013
+ target.name == 'quantity'
1014
+ ? Number(inputVal) * this.taxInvoiceItems[idx]['unitPrice']
1015
+ : Number(inputVal) * this.taxInvoiceItems[idx]['quantity']
1016
+ this.taxInvoiceItems[idx]['supplyPrice'] = supplyPrice
1017
+ this.taxInvoiceItems[idx]['taxPrice'] = this.isTaxFree ? 0 : Math.floor(supplyPrice / 10)
1018
+ }
1019
+
1020
+ // 인풋에 값 넣기
1021
+ this.taxInvoiceItems[idx][target.name] = inputVal
1022
+
1023
+ // 아이템 금액에 관련된 필드 수정시 합계 계산
1024
+ if (['quantity', 'unitPrice', 'supplyPrice', 'taxPrice'].includes(target.name)) {
1025
+ this.taxInvoiceItems = [...this.taxInvoiceItems]
1026
+ this._setTaxInvoiceSummaryCalc()
1027
+ }
1028
+ }
1029
+
1030
+ private async _openSupplierPopup() {
1031
+ const response = await client.query({
1032
+ query: gql`
1033
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
1034
+ responses: taxInvoiceApis(filters: $filters, pagination: $pagination, sortings: $sortings) {
1035
+ items {
1036
+ id
1037
+ name: provider
1038
+ businessRegistrationNumber
1039
+ branchIdentification
1040
+ businessName
1041
+ representativeName
1042
+ address
1043
+ businessType
1044
+ industryType
1045
+ department
1046
+ contactName
1047
+ contactPhone
1048
+ contactEmail
1049
+ contactCellPhone
1050
+ }
1051
+ }
1052
+ }
1053
+ `
1054
+ })
1055
+
1056
+ const items = response.data.responses.items || []
1057
+ openPopup(
1058
+ html`
1059
+ <company-list-popup
1060
+ .companyList=${items}
1061
+ @selected=${e => {
1062
+ this.supplier = {
1063
+ supplierBusinessRegistrationCategory: BusinessRegistrationCategory.BUSINESS_REGISTRATION_NUMBER,
1064
+ supplierBusinessRegistrationNumber: e.detail.businessRegistrationNumber || '',
1065
+ supplierBranchIdentification: e.detail.branchIdentification || '',
1066
+ supplierBusinessName: e.detail.businessName || '',
1067
+ supplierRepresentativeName: e.detail.representativeName || '',
1068
+ supplierAddress: e.detail.address || '',
1069
+ supplierBusinessType: e.detail.businessType || '',
1070
+ supplierIndustryType: e.detail.industryType || '',
1071
+ supplierDepartment: e.detail.department || '',
1072
+ supplierContactName: e.detail.contactName || '',
1073
+ supplierContactPhone: e.detail.contactPhone || '',
1074
+ supplierContactEmail: e.detail.contactEmail || '',
1075
+ supplierContactCellPhone: e.detail.contactCellPhone || ''
1076
+ }
1077
+
1078
+ history.back()
1079
+ }}
1080
+ >
1081
+ </company-list-popup>
1082
+ `,
1083
+ {
1084
+ backdrop: true,
1085
+ size: 'large',
1086
+ title: i18next.t('title.company list popup')
1087
+ }
1088
+ )
1089
+ }
1090
+
1091
+ private async _openBuyerPopup() {
1092
+ const response = await client.query({
1093
+ query: gql`
1094
+ query TaxInvoiceBuyerList {
1095
+ responses: taxInvoiceBuyerList {
1096
+ id
1097
+ name
1098
+ businessRegistrationNumber
1099
+ branchIdentification
1100
+ businessName
1101
+ representativeName
1102
+ address
1103
+ businessType
1104
+ industryType
1105
+ department
1106
+ contactName
1107
+ contactPhone
1108
+ contactEmail
1109
+ contactCellPhone
1110
+ }
1111
+ }
1112
+ `
1113
+ })
1114
+
1115
+ const items = response.data.responses || []
1116
+ openPopup(
1117
+ html`
1118
+ <company-list-popup
1119
+ .companyList=${items}
1120
+ @selected=${e => {
1121
+ this.buyer = {
1122
+ buyerBusinessRegistrationCategory: BusinessRegistrationCategory.BUSINESS_REGISTRATION_NUMBER,
1123
+ buyerBusinessRegistrationNumber: e.detail.businessRegistrationNumber || '',
1124
+ buyerBranchIdentification: e.detail.branchIdentification || '',
1125
+ buyerBusinessName: e.detail.businessName || '',
1126
+ buyerRepresentativeName: e.detail.representativeName || '',
1127
+ buyerAddress: e.detail.address || '',
1128
+ buyerBusinessType: e.detail.businessType || '',
1129
+ buyerIndustryType: e.detail.industryType || '',
1130
+ buyer1stDepartment: e.detail.department || '',
1131
+ buyer1stContactName: e.detail.contactName || '',
1132
+ buyer1stContactPhone: e.detail.contactPhone || '',
1133
+ buyer1stContactEmail: e.detail.contactEmail || '',
1134
+ buyer1stContactCellPhone: e.detail.contactCellPhone || ''
1135
+ }
1136
+
1137
+ history.back()
1138
+ }}
1139
+ >
1140
+ </company-list-popup>
1141
+ `,
1142
+ {
1143
+ backdrop: true,
1144
+ size: 'large',
1145
+ title: i18next.t('title.company list popup')
1146
+ }
1147
+ )
1148
+ }
1149
+
1150
+ private async _onAddInvoiceItem(e: Event) {
1151
+ e.preventDefault()
1152
+ if (this.taxInvoiceItems.length >= 16) {
1153
+ await CustomAlert({
1154
+ type: 'warning',
1155
+ title: '품목은 최대 16개까지 추가 가능합니다.',
1156
+ text: '품목은 최대 16개까지 추가 가능합니다.',
1157
+ confirmButton: { text: i18next.t('button.confirm') }
1158
+ })
1159
+
1160
+ return
1161
+ }
1162
+
1163
+ this.taxInvoiceItems = [...this.taxInvoiceItems, { ...this.defaultTaxInvoiceItem, sequence: new Date().getTime() }]
1164
+ }
1165
+
1166
+ private _onRemoveInvoiceItem(e: Event, sequence: number) {
1167
+ e.preventDefault()
1168
+ this.taxInvoiceItems = [...this.taxInvoiceItems.filter(item => item.sequence != sequence)]
1169
+ this._setTaxInvoiceSummaryCalc()
1170
+ }
1171
+
1172
+ private _setTaxInvoiceSummaryCalc() {
1173
+ const supplyPrice: number = this.taxInvoiceItems.reduce((prev, item) => prev + Number(item.supplyPrice), 0) || 0
1174
+ const taxPrice: number = this.isTaxFree
1175
+ ? 0
1176
+ : this.taxInvoiceItems.reduce((prev, item) => prev + Number(item.taxPrice), 0) || 0
1177
+ const totalAmount: number = supplyPrice + taxPrice
1178
+
1179
+ this.taxInvoice = { ...this.taxInvoice, supplyAmount: supplyPrice, taxAmount: taxPrice, totalAmount: totalAmount }
1180
+ }
1181
+ }