django-barobill 0.1.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include django_barobill/static *
4
+ recursive-include django_barobill/templates *
5
+ recursive-include django_barobill/migrations *
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.2
2
+ Name: django-barobill
3
+ Version: 0.1.0
4
+ Summary: A reusable Django app for integration with BaroBill services.
5
+ Home-page: https://github.com/cuhong/django-barobill
6
+ Author: cuhong
7
+ Author-email: hongcoilhouse@gmail.com
8
+ License: MIT License
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Django :: 4.0
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: django>=4.0
21
+ Requires-Dist: requests==2.32.3
22
+ Requires-Dist: zeep==4.3.1
23
+ Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: classifier
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: license
29
+ Dynamic: requires-dist
30
+ Dynamic: requires-python
31
+ Dynamic: summary
File without changes
@@ -0,0 +1,2 @@
1
+ # django_barobill/__init__.py
2
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from django.contrib import admin
2
+
3
+ # Register your models here.
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjangoBarobillConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'django_barobill'
@@ -0,0 +1,39 @@
1
+ from django.db import models
2
+
3
+ class BankAccountCollectCycle(models.TextChoices):
4
+ MINUTE10 = 'MINUTE10', '10분'
5
+ MINUTE30 = 'MINUTE30', '30분'
6
+ HOUR1 = 'HOUR1', '1시간'
7
+ HOUR4 = 'HOUR4', '4시간'
8
+ DAY1 = 'DAY1', '1일'
9
+
10
+ class BankAccountBank(models.TextChoices):
11
+ KB = 'KB', '국민은행'
12
+ SHINHAN = 'SHINHAN', '신한은행'
13
+ NH = 'NH', '농협은행'
14
+ HANA = 'HANA', '하나은행'
15
+ SC = 'SC', '제일은행'
16
+ WOORI = 'WOORI', '우리은행'
17
+ IBK = 'IBK', '기업은행'
18
+ KDB = 'KDB', '산업은행'
19
+ KFCC = 'KFCC', '새마을금고'
20
+ CITI = 'CITI', '씨티은행'
21
+ SUHYUP = 'SUHYUP', '수협은행'
22
+ CU = 'CU', '신협은행'
23
+ EPOST = 'EPOST', '우체국'
24
+ KJBANK = 'KJBANK', '광주은행'
25
+ JBBANK = 'JBBANK', '전북은행'
26
+ DGB = 'DGB', '대구은행'
27
+ BUSANBANK = 'BUSANBANK', '부산은행'
28
+ KNBANK = 'KNBANK', '경남은행'
29
+ EJEJUBANK = 'EJEJUBANK', '제주은행'
30
+ KBANK = 'KBANK', '케이뱅크'
31
+
32
+ class BankAccountAccountType(models.TextChoices):
33
+ C = 'C', '법인계좌'
34
+ P = 'P', '개인계좌'
35
+
36
+ class BankAccountTransactionDirection(models.TextChoices):
37
+ Deposit = 'D', '입금'
38
+ Withdraw = 'W', '출금'
39
+ ETC = 'E', '기타'
@@ -0,0 +1,67 @@
1
+ error_dict = {
2
+ # 커스텀
3
+ 901: "해지된 계좌 입니다",
4
+ 902: "해지되지 않은 계좌 입니다",
5
+ # 기본 오류코드
6
+ -10000: "알 수 없는 오류 발생. API 호출 중 서버오류가 발생한 경우입니다. 바로빌로 문의바랍니다.",
7
+ -10003: "연동서비스가 점검 중입니다.",
8
+ -10004: "해당 기능은 더 이상 사용되지 않습니다.",
9
+ -10007: "해당 기능을 사용할 수 없습니다.",
10
+ -10005: "최대 100건까지만 사용하실 수 있습니다.",
11
+ -10006: "최대 1000건까지만 사용하실 수 있습니다.",
12
+ -10008: "날짜형식이 잘못되었습니다.",
13
+ -10148: "조회 기간이 잘못되었습니다.",
14
+ -40001: "파일을 찾을 수 없습니다.",
15
+ -40002: "빈 파일입니다(0byte).",
16
+ # 스크래핑 공통
17
+ -51001: "서비스가 신청되지 않았습니다.",
18
+ -51002: "이미 신청되어 있습니다.",
19
+ -51003: "이미 해지되었습니다.",
20
+ -51004: "해지 상태가 아닙니다.",
21
+ -51005: "해지 당월에만 해지 취소할 수 있습니다.",
22
+ -51006: "해지 당월에는 재신청 할 수 없습니다. 해지 취소로 진행해주세요.",
23
+ -51007: "테스트베드는 최대 2개까지만 등록할 수 있습니다.",
24
+ -51008: "이미 수집중입니다.",
25
+ # 연동정보 관련
26
+ -10002: "해당 인증키를 찾을 수 없습니다.",
27
+ -10001: "해당 인증키와 연결된 연계사가 아닙니다.",
28
+ -24005: "사업자번호와 아이디가 맞지 않습니다.",
29
+ -50001: "계좌를 찾을 수 없습니다.",
30
+ -50002: "계좌를 조회할 권한이 없습니다.",
31
+ -50004: "일일 즉시조회 횟수(10회)를 초과하였습니다.",
32
+ -50211: "수집주기가 잘못 입력되었습니다.",
33
+ -50212: "은행 코드가 잘못 입력되었습니다.",
34
+ -50213: "계좌유형이 잘못 입력되었습니다.",
35
+ -50214: "계좌번호가 잘못 입력되었습니다.",
36
+ -50215: "계좌 비밀번호가 잘못 입력되었습니다.",
37
+ -50216: "계좌 비밀번호를 입력하지 않아야하는 은행입니다.",
38
+ -50217: "빠른조회 아이디가 잘못 입력되었습니다.",
39
+ -50218: "빠른조회 아이디를 입력하지 않아야하는 은행입니다.",
40
+ -50219: "빠른조회 비밀번호가 잘못 입력되었습니다.",
41
+ -50220: "빠른조회 비밀번호를 입력하지 않아야하는 은행입니다.",
42
+ -50221: "사업자번호 또는 주민번호(앞6자리)가 잘못 입력되었습니다.",
43
+ -50222: "사업자번호 또는 주민번호(앞6자리)를 입력하지 않아야하는 은행입니다.",
44
+ -50223: "유효한 계좌정보가 아닙니다. 계좌정보를 확인해 주시기 바랍니다.",
45
+ -50224: "계좌정보 검증에 실패하였습니다. 잠시 후 다시 시도해 주시기 바랍니다.",
46
+ -50225: "이미 등록된 계좌번호입니다.",
47
+ -50226: "간편조회(빠른조회)서비스에 등록되지 않은 계좌입니다.",
48
+ -50251: "입출금내역 키(TransRefKey)가 잘못 입력되었습니다.",
49
+ -50252: "계좌 입출금내역을 찾을 수 없습니다."
50
+ }
51
+
52
+
53
+ class BarobillBaseError(Exception):
54
+ def __init__(self, code, message=None):
55
+ self.code = int(code)
56
+ self.message = message or error_dict.get(self.code, f"알 수 없는 오류 코드 : {self.code}")
57
+
58
+ def __str__(self):
59
+ return self.message
60
+
61
+
62
+ class BarobillAPIError(BarobillBaseError):
63
+ pass
64
+
65
+
66
+ class BarobillError(BarobillBaseError):
67
+ pass
@@ -0,0 +1,78 @@
1
+ # Generated by Django 5.1.4 on 2025-01-16 01:16
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = [
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='Partner',
17
+ fields=[
18
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+ ('name', models.CharField(max_length=255, unique=True, verbose_name='파트너사명')),
20
+ ('brn', models.CharField(max_length=10, unique=True, verbose_name='사업자등록번호')),
21
+ ('api_key', models.CharField(max_length=36, verbose_name='인증키')),
22
+ ('userid', models.CharField(max_length=256, verbose_name='파트너사 사용자 아이디')),
23
+ ('dev', models.BooleanField(default=False, verbose_name='테스트모드')),
24
+ ],
25
+ options={
26
+ 'verbose_name': '파트너',
27
+ 'verbose_name_plural': '파트너',
28
+ },
29
+ ),
30
+ migrations.CreateModel(
31
+ name='BankAccount',
32
+ fields=[
33
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
34
+ ('collect_cycle', models.CharField(choices=[('MINUTE10', '10분'), ('MINUTE30', '30분'), ('HOUR1', '1시간'), ('HOUR4', '4시간'), ('DAY1', '1일')], max_length=10, verbose_name='수집주기')),
35
+ ('bank', models.CharField(choices=[('KB', '국민은행'), ('SHINHAN', '신한은행'), ('NH', '농협은행'), ('HANA', '하나은행'), ('SC', '제일은행'), ('WOORI', '우리은행'), ('IBK', '기업은행'), ('KDB', '산업은행'), ('KFCC', '새마을금고'), ('CITI', '씨티은행'), ('SUHYUP', '수협은행'), ('CU', '신협은행'), ('EPOST', '우체국'), ('KJBANK', '광주은행'), ('JBBANK', '전북은행'), ('DGB', '대구은행'), ('BUSANBANK', '부산은행'), ('KNBANK', '경남은행'), ('EJEJUBANK', '제주은행'), ('KBANK', '케이뱅크')], max_length=10, verbose_name='은행')),
36
+ ('account_type', models.CharField(choices=[('C', '법인계좌'), ('P', '개인계좌')], max_length=1, verbose_name='계좌유형')),
37
+ ('account_no', models.CharField(max_length=50, verbose_name='계좌번호')),
38
+ ('alias', models.CharField(default=None, max_length=50, null=True, verbose_name='별칭')),
39
+ ('usage', models.CharField(default=None, max_length=50, null=True, verbose_name='용도')),
40
+ ('is_stop', models.BooleanField(default=False, verbose_name='해지')),
41
+ ('stop_date', models.DateField(default=None, null=True, verbose_name='해지일')),
42
+ ('partner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='django_barobill.partner', verbose_name='파트너')),
43
+ ],
44
+ options={
45
+ 'verbose_name': '계좌',
46
+ 'verbose_name_plural': '계좌',
47
+ },
48
+ ),
49
+ migrations.CreateModel(
50
+ name='BankAccountTransaction',
51
+ fields=[
52
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
53
+ ('trans_direction', models.CharField(choices=[('D', '입금'), ('W', '출금'), ('E', '기타')], max_length=1)),
54
+ ('deposit', models.BigIntegerField(default=None, null=True)),
55
+ ('withdraw', models.BigIntegerField(default=None, null=True)),
56
+ ('balance', models.BigIntegerField(default=0)),
57
+ ('trans_dt', models.DateTimeField()),
58
+ ('trans_type', models.CharField(default=None, max_length=50, null=True, verbose_name='입출금구분')),
59
+ ('trans_office', models.CharField(default=None, max_length=50, null=True, verbose_name='입출금취급점')),
60
+ ('trans_remark', models.CharField(default=None, max_length=50, null=True, verbose_name='입출금비고')),
61
+ ('mgt_remark_1', models.CharField(default=None, max_length=50, null=True, verbose_name='비고1')),
62
+ ('trans_ref_key', models.CharField(max_length=24, verbose_name='입출금내역 키')),
63
+ ('memo', models.CharField(default=None, max_length=100, null=True, verbose_name='입출금내역 키')),
64
+ ('meta', models.JSONField(default=None, null=True, verbose_name='추가 정보')),
65
+ ('bank_account', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='django_barobill.bankaccount')),
66
+ ],
67
+ options={
68
+ 'verbose_name': '입출금 기록',
69
+ 'verbose_name_plural': '입출금 기록',
70
+ 'ordering': ['-trans_dt', 'bank_account'],
71
+ 'unique_together': {('bank_account', 'trans_ref_key')},
72
+ },
73
+ ),
74
+ migrations.AddConstraint(
75
+ model_name='bankaccount',
76
+ constraint=models.UniqueConstraint(fields=('partner', 'bank', 'account_no'), name='unique_partner_bank_account'),
77
+ ),
78
+ ]
@@ -0,0 +1,387 @@
1
+ import datetime
2
+ from typing import Optional
3
+
4
+ from django.db import models
5
+ from django.utils import timezone
6
+ from zeep import Client
7
+
8
+ from django_barobill.choices import BankAccountCollectCycle, BankAccountBank, BankAccountAccountType, BankAccountTransactionDirection
9
+ from django_barobill.errors import BarobillAPIError, BarobillError
10
+ from django_barobill.parsers import bank_account_log_parser
11
+
12
+
13
+ class BankHelper:
14
+ def __init__(self, partner):
15
+ self.partner = partner
16
+ self.client = self._get_client()
17
+
18
+ def _get_client(self):
19
+ if self.partner.dev:
20
+ endpoint = "https://testws.baroservice.com/BANKACCOUNT.asmx?wsdl"
21
+ else:
22
+ endpoint = "https://ws.baroservice.com/BANKACCOUNT.asmx?wsdl"
23
+ return Client(endpoint)
24
+
25
+ def get_bank_account_management_url(self):
26
+ return self.client.service.GetBankAccountManagementURL(
27
+ CERTKEY=self.partner.api_key,
28
+ CorpNum=self.partner.brn,
29
+ ID=self.partner.userid,
30
+ PWD=''
31
+ )
32
+
33
+ def get_bank_account_list(self, avail_only: bool):
34
+ response = self.client.service.GetBankAccountEx(
35
+ CERTKEY=self.partner.api_key,
36
+ CorpNum=self.partner.brn,
37
+ AvailOnly=1 if avail_only is True else 0,
38
+ )
39
+ if response is None:
40
+ return []
41
+ return response
42
+
43
+ def register_bank_account(
44
+ self,
45
+ alias: str,
46
+ collect_cycle: BankAccountCollectCycle,
47
+ bank: BankAccountBank,
48
+ account_type: BankAccountAccountType,
49
+ account_no: str,
50
+ password: str,
51
+ usage: Optional[str] = None,
52
+ web_id: Optional[str] = None,
53
+ web_pwd: Optional[str] = None,
54
+ identity_num: Optional[str] = None
55
+ ):
56
+ # need test
57
+ result = self.client.service.RegisterBankAccount(
58
+ CERTKEY=self.partner.api_key,
59
+ CorpNum=self.partner.brn,
60
+ CollectCycle=collect_cycle,
61
+ Bank=bank,
62
+ BankAccountType=account_type,
63
+ BankAccountNum=account_no,
64
+ BankAccountPwd=password,
65
+ WebId=web_id,
66
+ WebPwd=web_pwd,
67
+ IdentityNum=identity_num,
68
+ Alias=alias,
69
+ Usage=usage,
70
+ )
71
+ if result != 1:
72
+ raise BarobillAPIError(result)
73
+ account, created = BankAccount.objects.update_or_create(
74
+ partner=self.partner, account_no=account_no, defaults=dict(
75
+ collect_cycle=collect_cycle, bank=bank, account_type=account_type, alias=alias, usage=usage, is_delete=False
76
+ )
77
+ )
78
+ return account
79
+
80
+ def update_bank_accounts(self):
81
+ account_list = self.get_bank_account_list(avail_only=False)
82
+ account_no_list = [acc.BankAccountNum for acc in account_list]
83
+ self.partner.bankaccount_set.filter(
84
+ account_no__in=account_no_list
85
+ ).update(is_stop=False, stop_date=None, )
86
+ self.partner.bankaccount_set.exclude(
87
+ account_no__in=account_no_list
88
+ ).update(is_stop=True, stop_date=timezone.localdate())
89
+
90
+
91
+ class Partner(models.Model):
92
+ class Meta:
93
+ verbose_name = '파트너'
94
+ verbose_name_plural = verbose_name
95
+
96
+ name = models.CharField(max_length=255, unique=True, verbose_name='파트너사명')
97
+ brn = models.CharField(max_length=10, unique=True, verbose_name='사업자등록번호')
98
+ api_key = models.CharField(max_length=36, verbose_name='인증키')
99
+ userid = models.CharField(max_length=256, verbose_name='파트너사 사용자 아이디')
100
+ dev = models.BooleanField(default=False, null=False, verbose_name='테스트모드')
101
+
102
+ def __init__(self, *args, **kwargs):
103
+ super().__init__(*args, **kwargs)
104
+ self.bank = BankHelper(self)
105
+
106
+
107
+ class BankAccount(models.Model):
108
+ class Meta:
109
+ verbose_name = '계좌'
110
+ verbose_name_plural = verbose_name
111
+ constraints = [
112
+ models.UniqueConstraint(fields=['partner', 'bank', 'account_no'], name='unique_partner_bank_account'),
113
+ ]
114
+
115
+ partner = models.ForeignKey(Partner, null=False, blank=False, verbose_name='파트너', on_delete=models.PROTECT)
116
+ collect_cycle = models.CharField(
117
+ max_length=10, null=False, blank=False, choices=BankAccountCollectCycle.choices, verbose_name='수집주기'
118
+ )
119
+ bank = models.CharField(
120
+ max_length=10, null=False, blank=False, choices=BankAccountBank.choices, verbose_name='은행'
121
+ )
122
+ account_type = models.CharField(
123
+ max_length=1, null=False, blank=False, choices=BankAccountAccountType.choices, verbose_name='계좌유형'
124
+ )
125
+ account_no = models.CharField(
126
+ max_length=50, null=False, blank=False, verbose_name='계좌번호'
127
+ )
128
+ alias = models.CharField(max_length=50, null=True, blank=False, default=None, verbose_name='별칭')
129
+ usage = models.CharField(max_length=50, null=True, blank=False, default=None, verbose_name='용도')
130
+ is_stop = models.BooleanField(default=False, null=False, verbose_name='해지')
131
+ stop_date = models.DateField(null=True, blank=False, default=None, verbose_name='해지일')
132
+
133
+ def update_bank_account(
134
+ self,
135
+ password: Optional[str] = None,
136
+ web_id: Optional[str] = None,
137
+ web_pwd: Optional[str] = None,
138
+ identity_num: Optional[str] = None,
139
+ alias: Optional[str] = None,
140
+ usage: Optional[str] = None
141
+ ):
142
+ request_data = dict(
143
+ CERTKEY=self.partner.api_key,
144
+ CorpNum=self.partner.brn,
145
+ BankAccountNum=self.account_no
146
+ )
147
+ if password:
148
+ request_data['BankAccountPwd'] = password
149
+ if web_id:
150
+ request_data['WebId'] = web_id
151
+ if web_pwd:
152
+ request_data['WebPwd'] = web_pwd
153
+ if identity_num:
154
+ request_data['IdentityNum'] = identity_num
155
+ if alias:
156
+ request_data['Alias'] = alias
157
+ if usage:
158
+ request_data['Usage'] = usage
159
+ result = self.partner.bank.client.service.UpdateBankAccount(**request_data)
160
+ if result != 1:
161
+ raise BarobillAPIError(result)
162
+ save_fields = []
163
+ if self.alias != alias and alias is not None:
164
+ self.alias = alias
165
+ save_fields.append('alias')
166
+ if self.usage != usage and usage is not None:
167
+ self.usage = usage
168
+ save_fields.append('usage')
169
+ if len(save_fields) != 0:
170
+ self.save(update_fields=save_fields)
171
+
172
+ def stop_bank_account(self):
173
+ if self.is_stop is True:
174
+ raise BarobillError(901)
175
+ request_data = dict(
176
+ CERTKEY=self.partner.api_key,
177
+ CorpNum=self.partner.brn,
178
+ BankAccountNum=self.account_no
179
+ )
180
+ result = self.partner.bank.client.service.StopBankAccount(**request_data)
181
+ if result != 1:
182
+ raise BarobillAPIError(result)
183
+ self.is_stop = True
184
+ self.stop_date = timezone.localdate()
185
+ self.save(update_fields=['is_stop', 'stop_date'])
186
+
187
+ def cancel_stop_bank(self):
188
+ if self.is_stop is False:
189
+ raise BarobillError(902)
190
+ request_data = dict(
191
+ CERTKEY=self.partner.api_key,
192
+ CorpNum=self.partner.brn,
193
+ BankAccountNum=self.account_no
194
+ )
195
+ result = self.partner.bank.client.service.CancelStopBankAccount(**request_data)
196
+ if result != 1:
197
+ raise BarobillAPIError(result)
198
+ self.is_stop = False
199
+ self.stop_date = None
200
+ self.save(update_fields=['is_stop', 'stop_date'])
201
+
202
+ def re_register_bank_account(self):
203
+ if self.is_stop is False:
204
+ raise BarobillError(902)
205
+ request_data = dict(
206
+ CERTKEY=self.partner.api_key,
207
+ CorpNum=self.partner.brn,
208
+ BankAccountNum=self.account_no
209
+ )
210
+ result = self.partner.bank.client.service.ReRegistBankAccount(**request_data)
211
+ if result != 1:
212
+ raise BarobillAPIError(result)
213
+ self.is_stop = False
214
+ self.stop_date = None
215
+ self.save(update_fields=['is_stop', 'stop_date'])
216
+
217
+ def __update_log(self, log_list):
218
+ """
219
+ BankAccountLogEx2 의 list 값을 거래 기록에 등록한다.
220
+ :param log_list: [BankAccountLogEx2]
221
+ :return:None
222
+ """
223
+ parsed_log_list = map(bank_account_log_parser, log_list)
224
+ transactions = BankAccountTransaction.objects.bulk_create(
225
+ [BankAccountTransaction(bank_account=self, **parsed_log) for parsed_log in parsed_log_list],
226
+ ignore_conflicts=True
227
+ )
228
+
229
+ def update_date_log(self, date_string: str):
230
+ """
231
+ 지정한 날짜의 로그를 불러와 데이터베이스에 저장한다.
232
+ :param date_string: YYYYMMDD 형태의 한국 날짜 string
233
+ :return: None
234
+ """
235
+ page = 1
236
+ log_list = []
237
+ while True:
238
+ result = self.get_daily_log(date_string, page)
239
+ log_list += [] if result.BankAccountLogList is None else result.BankAccountLogList.BankAccountLogEx2
240
+ if result.MaxPageNum == page:
241
+ break
242
+ page += 1
243
+ self.__update_log(log_list)
244
+
245
+ def update_today_log(self):
246
+ """
247
+ 오늘 거래 내역을 불러오고, 데이터베이스에 저장한다.
248
+ :return: None
249
+ """
250
+ date_string = timezone.localdate().strftime('%Y%m%d')
251
+ self.update_date_log(date_string)
252
+
253
+ def update_yesterday_log(self):
254
+ """
255
+ 어제의 거래 내역을 불러오고, 데이터베이스에 저장한다.
256
+ :return: None
257
+ """
258
+ date_string = (timezone.localdate() - datetime.timedelta(days=1)).strftime('%Y%m%d')
259
+ self.update_date_log(date_string)
260
+
261
+ def get_log(
262
+ self,
263
+ start_date: str,
264
+ end_date: str,
265
+ page: int,
266
+ direction: int = 1,
267
+ per_page: int = 100,
268
+ order: int = 1
269
+ ):
270
+ """
271
+ 주어진 기간에 대한 거래 내역을 불러온다.
272
+ https://dev.barobill.co.kr/docs/references/%EA%B3%84%EC%A2%8C%EC%A1%B0%ED%9A%8C-API#GetPeriodBankAccountLogEx2
273
+ :param start_date: 한국시간 YYYYMMDD 형태의 조회 시작일 string.
274
+ :param end_date: 한국시간 YYYYMMDD 형태의 조회 종료일 string.
275
+ :param page: 조회할 페이지 수
276
+ :param direction: 거래 유형. 1 전체, 2 입금, 3출금
277
+ :param per_page: 페이지 당 결과 표시 수(최대 100개)
278
+ :param order: 거래일시 정렬 순서. 1 오름차순, 2. 내림차순
279
+ :return: barobill API 결과를 그대로 반환한다.(https://dev.barobill.co.kr/docs/references/%EA%B3%84%EC%A2%8C%EC%A1%B0%ED%9A%8C-API#PagedBankAccountLogEx2)
280
+ """
281
+ request_data = dict(
282
+ CERTKEY=self.partner.api_key,
283
+ CorpNum=self.partner.brn,
284
+ ID=self.partner.userid,
285
+ BankAccountNum=self.account_no,
286
+ StartDate=start_date,
287
+ EndDate=end_date,
288
+ TransDirection=direction,
289
+ CountPerPage=per_page,
290
+ CurrentPage=page,
291
+ OrderDirection=order # 1 오름차순, 2 내림차순
292
+ )
293
+ result = self.partner.bank.client.service.GetPeriodBankAccountLogEx2(**request_data)
294
+ if result.CurrentPage <= 0:
295
+ raise BarobillAPIError(result.CurrentPage)
296
+ return result
297
+
298
+ def get_daily_log(
299
+ self,
300
+ base_date: str,
301
+ page: int,
302
+ direction: int = 1,
303
+ per_page: int = 100,
304
+ order: int = 1
305
+ ):
306
+ """
307
+ 주어진 일자의 거래 내역을 불러온다.
308
+ https://dev.barobill.co.kr/docs/references/%EA%B3%84%EC%A2%8C%EC%A1%B0%ED%9A%8C-API#GetDailyBankAccountLogEx2
309
+ :param base_date: 한국시간 YYYYMMDD 형태의 조회일 string.
310
+ :param page: 조회할 페이지 수
311
+ :param direction: 거래 유형. 1 전체, 2 입금, 3출금
312
+ :param per_page: 페이지 당 결과 표시 수(최대 100개)
313
+ :param order: 거래일시 정렬 순서. 1 오름차순, 2. 내림차순
314
+ :return: barobill API 결과를 그대로 반환한다.(https://dev.barobill.co.kr/docs/references/%EA%B3%84%EC%A2%8C%EC%A1%B0%ED%9A%8C-API#PagedBankAccountLogEx2)
315
+ """
316
+ request_data = dict(
317
+ CERTKEY=self.partner.api_key,
318
+ CorpNum=self.partner.brn,
319
+ ID=self.partner.userid,
320
+ BankAccountNum=self.account_no,
321
+ BaseDate=base_date,
322
+ TransDirection=direction,
323
+ CountPerPage=per_page,
324
+ CurrentPage=page,
325
+ OrderDirection=order # 1 오름차순, 2 내림차순
326
+ )
327
+ result = self.partner.bank.client.service.GetDailyBankAccountLogEx2(**request_data)
328
+ if result.CurrentPage <= 0:
329
+ raise BarobillAPIError(result.CurrentPage)
330
+ return result
331
+
332
+
333
+ def get_monthly_log(
334
+ self,
335
+ base_month: str,
336
+ page: int,
337
+ direction: int = 1,
338
+ per_page: int = 100,
339
+ order: int = 1
340
+ ):
341
+ """
342
+ 주어진 일자의 거래 내역을 불러온다.
343
+ https://dev.barobill.co.kr/docs/references/%EA%B3%84%EC%A2%8C%EC%A1%B0%ED%9A%8C-API#GetMonthlyBankAccountLogEx2
344
+ :param base_month: 한국시간 YYYYMM 형태의 조회월 string.
345
+ :param page: 조회할 페이지 수
346
+ :param direction: 거래 유형. 1 전체, 2 입금, 3출금
347
+ :param per_page: 페이지 당 결과 표시 수(최대 100개)
348
+ :param order: 거래일시 정렬 순서. 1 오름차순, 2. 내림차순
349
+ :return: barobill API 결과를 그대로 반환한다.(https://dev.barobill.co.kr/docs/references/%EA%B3%84%EC%A2%8C%EC%A1%B0%ED%9A%8C-API#PagedBankAccountLogEx2)
350
+ """
351
+ request_data = dict(
352
+ CERTKEY=self.partner.api_key,
353
+ CorpNum=self.partner.brn,
354
+ ID=self.partner.userid,
355
+ BankAccountNum=self.account_no,
356
+ BaseMonth=base_month,
357
+ TransDirection=direction,
358
+ CountPerPage=per_page,
359
+ CurrentPage=page,
360
+ OrderDirection=order # 1 오름차순, 2 내림차순
361
+ )
362
+ result = self.partner.bank.client.service.GetDailyBankAccountLogEx2(**request_data)
363
+ if result.CurrentPage <= 0:
364
+ raise BarobillAPIError(result.CurrentPage)
365
+ return result
366
+
367
+
368
+ class BankAccountTransaction(models.Model):
369
+ class Meta:
370
+ verbose_name = '입출금 기록'
371
+ verbose_name_plural = verbose_name
372
+ unique_together = ('bank_account', 'trans_ref_key')
373
+ ordering = ['-trans_dt', 'bank_account']
374
+
375
+ bank_account = models.ForeignKey(BankAccount, null=False, blank=False, on_delete=models.PROTECT)
376
+ trans_direction = models.CharField(max_length=1, null=False, blank=False, choices=BankAccountTransactionDirection.choices)
377
+ deposit = models.BigIntegerField(null=True, blank=False, default=None)
378
+ withdraw = models.BigIntegerField(null=True, blank=False, default=None)
379
+ balance = models.BigIntegerField(null=False, blank=False, default=0)
380
+ trans_dt = models.DateTimeField(null=False, blank=False)
381
+ trans_type = models.CharField(max_length=50, null=True, blank=False, default=None, verbose_name='입출금구분')
382
+ trans_office = models.CharField(max_length=50, null=True, blank=False, default=None, verbose_name='입출금취급점')
383
+ trans_remark = models.CharField(max_length=50, null=True, blank=False, default=None, verbose_name='입출금비고')
384
+ mgt_remark_1 = models.CharField(max_length=50, null=True, blank=False, default=None, verbose_name='비고1')
385
+ trans_ref_key = models.CharField(max_length=24, null=False, blank=False, verbose_name='입출금내역 키')
386
+ memo = models.CharField(max_length=100, null=True, blank=False, default=None, verbose_name='입출금내역 키')
387
+ meta = models.JSONField(null=True, blank=False, default=None, verbose_name='추가 정보')
@@ -0,0 +1,29 @@
1
+ import datetime
2
+
3
+ from django_barobill.utils import KST
4
+
5
+
6
+ def bank_account_log_parser(item):
7
+ """
8
+ BankAccountLogEx2를 데이터베이스에 적재할 수 있는 형태로 파싱한다.
9
+ :param item: BankAccountLogEx2를
10
+ :return: BankAccountTransaction
11
+ """
12
+ from django_barobill.choices import BankAccountTransactionDirection
13
+ return dict(
14
+ trans_direction={
15
+ "입금": BankAccountTransactionDirection.Deposit,
16
+ "출금": BankAccountTransactionDirection.Withdraw,
17
+ "기타": BankAccountTransactionDirection.ETC,
18
+ }.get(item.TransDirection),
19
+ deposit=int(item.Deposit),
20
+ withdraw=int(item.Withdraw),
21
+ balance=int(item.Balance),
22
+ trans_dt=KST.localize(datetime.datetime.strptime(item.TransDT, "%Y%m%d%H%M%S")),
23
+ trans_type=item.TransType,
24
+ trans_office=item.TransOffice,
25
+ trans_remark=item.TransRemark,
26
+ mgt_remark_1=item.MgtRemark1,
27
+ trans_ref_key=item.TransRefKey,
28
+ memo=item.Memo,
29
+ )
@@ -0,0 +1,48 @@
1
+ import datetime
2
+
3
+ from django.test import TestCase
4
+ from django.utils import timezone
5
+
6
+ from django_barobill.choices import BankAccountBank, BankAccountCollectCycle, BankAccountAccountType
7
+ from django_barobill.models import Partner, BankAccount
8
+ from django_barobill.parsers import bank_account_log_parser
9
+
10
+
11
+ # Create your tests here.
12
+ class TestBankAccount(TestCase):
13
+ def setUp(self):
14
+ self.partner = Partner.objects.create(
15
+ name='지구하다ERP',
16
+ brn='2978601887',
17
+ api_key='0830C49B-71EA-44E0-81E0-DAAD883B3FC9',
18
+ userid='cuhong',
19
+ dev=True
20
+ )
21
+ self.bank = BankAccountBank.HANA
22
+ self.account_no = '26091002164604'
23
+ self.bank_account = BankAccount.objects.create(
24
+ partner=self.partner,
25
+ collect_cycle=BankAccountCollectCycle.MINUTE10,
26
+ bank=self.bank,
27
+ account_type=BankAccountAccountType.C,
28
+ account_no=self.account_no,
29
+ )
30
+
31
+ def test_get_yesterday_log(self):
32
+ date_string = (timezone.localdate() - datetime.timedelta(days=0)).strftime('%Y%m%d')
33
+ log_list = []
34
+ page = 1
35
+ while True:
36
+ result = self.bank_account.get_daily_log(date_string, page)
37
+ log_list += [] if result.BankAccountLogList is None else result.BankAccountLogList.BankAccountLogEx2
38
+ if result.MaxPageNum == page:
39
+ break
40
+ page += 1
41
+ for log in log_list:
42
+ print(bank_account_log_parser(log))
43
+
44
+ def test_update_today_log(self):
45
+ self.bank_account.update_today_log()
46
+
47
+ ba = BankAccount.objects.first()
48
+ ba.update_yesterday_log()
@@ -0,0 +1,9 @@
1
+ import pytz
2
+ from zeep import Client
3
+
4
+ KST = pytz.timezone('Asia/Seoul')
5
+
6
+ class ServiceWrapper:
7
+ def __init__(self, endpoint):
8
+ self.endpoint = endpoint
9
+ self.client = Client(endpoint)
@@ -0,0 +1,3 @@
1
+ from django.shortcuts import render
2
+
3
+ # Create your views here.
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.2
2
+ Name: django-barobill
3
+ Version: 0.1.0
4
+ Summary: A reusable Django app for integration with BaroBill services.
5
+ Home-page: https://github.com/cuhong/django-barobill
6
+ Author: cuhong
7
+ Author-email: hongcoilhouse@gmail.com
8
+ License: MIT License
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Django :: 4.0
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: django>=4.0
21
+ Requires-Dist: requests==2.32.3
22
+ Requires-Dist: zeep==4.3.1
23
+ Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: classifier
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: license
29
+ Dynamic: requires-dist
30
+ Dynamic: requires-python
31
+ Dynamic: summary
@@ -0,0 +1,23 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ setup.cfg
5
+ setup.py
6
+ django_barobill/__init__.py
7
+ django_barobill/admin.py
8
+ django_barobill/apps.py
9
+ django_barobill/choices.py
10
+ django_barobill/errors.py
11
+ django_barobill/models.py
12
+ django_barobill/parsers.py
13
+ django_barobill/tests.py
14
+ django_barobill/utils.py
15
+ django_barobill/views.py
16
+ django_barobill.egg-info/PKG-INFO
17
+ django_barobill.egg-info/SOURCES.txt
18
+ django_barobill.egg-info/dependency_links.txt
19
+ django_barobill.egg-info/requires.txt
20
+ django_barobill.egg-info/top_level.txt
21
+ django_barobill/migrations/0001_initial.py
22
+ django_barobill/migrations/__init__.py
23
+ tests/test_app.py
@@ -0,0 +1,3 @@
1
+ django>=4.0
2
+ requests==2.32.3
3
+ zeep==4.3.1
@@ -0,0 +1 @@
1
+ django_barobill
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,31 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="django-barobill", # 패키지 이름 (PyPI에 등록될 이름)
5
+ version="0.1.0", # 초기 버전
6
+ packages=find_packages(exclude=["tests*", "docs*"]), # 'django_barobill' 폴더 포함
7
+ include_package_data=True, # MANIFEST.in 파일에 포함된 파일도 추가
8
+ license="MIT License", # 라이선스
9
+ description="A reusable Django app for integration with BaroBill services.",
10
+ long_description=open("README.md").read(),
11
+ long_description_content_type="text/markdown", # README의 마크다운 형식 지원
12
+ url="https://github.com/cuhong/django-barobill", # 깃허브 등의 URL
13
+ author="cuhong",
14
+ author_email="hongcoilhouse@gmail.com",
15
+ classifiers=[
16
+ "Environment :: Web Environment",
17
+ "Framework :: Django",
18
+ "Framework :: Django :: 4.0", # 해당 Django 버전
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python",
22
+ "Programming Language :: Python :: 3.8", # 최소 파이썬 버전
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ],
25
+ python_requires=">=3.8", # 지원 파이썬 버전
26
+ install_requires=[
27
+ "django>=4.0", # Django 패키지 요구사항
28
+ "requests==2.32.3",
29
+ "zeep==4.3.1"
30
+ ],
31
+ )
@@ -0,0 +1,7 @@
1
+ import unittest
2
+ from django_barobill.apps import DjangoBarobillConfig
3
+
4
+
5
+ class TestDjangoBarobill(unittest.TestCase):
6
+ def test_app_name(self):
7
+ self.assertEqual(DjangoBarobillConfig.name, "django_barobill")