Skip to content

Commit 14ffc0c

Browse files
Merge pull request #35 from usermicrodevices/develop
add doc cash api
2 parents 0623c47 + 8c770a9 commit 14ffc0c

5 files changed

Lines changed: 225 additions & 10 deletions

File tree

api/tests.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from django.contrib.auth import get_user_model
66
#from django.contrib.auth.models import Group
77
from django.contrib.auth.hashers import make_password
8-
from django.test import Client
98
from django.utils import timezone as django_timezone
9+
from django.test import Client
1010

1111
from html.parser import HTMLParser
1212

@@ -80,3 +80,22 @@ def test_products(self):
8080
print('Request♥', response.request)
8181
print('Response♡', response, response.headers)
8282
print('DATA⋆', eval(json.loads(response.content)))
83+
84+
def test_docs(self):
85+
print()
86+
url = '/api/docs/'
87+
print('⚽GET', url)
88+
response = self.client.get(url, headers={'X-CSRFToken':self.csrfmiddlewaretoken})
89+
self.assertEqual(response.status_code, 200)
90+
print('Request♥', response.request)
91+
print('Response♡', response, response.headers)
92+
print('DATA⋆', eval(json.loads(response.content.decode('utf8').replace('null', 'None'))))
93+
print()
94+
url = '/api/doc/cash/'
95+
data = json.dumps({'sum_final':1000, 'records':[{'product':1, 'count':10, 'price':45}, {'product':2, 'count':10, 'price':63}]})
96+
print('⚽POST', url, data)
97+
response = self.client.post(url, data, 'json', headers={'X-CSRFToken':self.csrfmiddlewaretoken})
98+
self.assertEqual(response.status_code, 200)
99+
print('Request♥', response.request)
100+
print('Response♡', response, response.headers)
101+
print('DATA⋆', json.loads(response.content))

api/urls.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@
77
path('login/', views.url_login),
88
path('logout/', views.url_logout),
99
path('products/', views.ProductsView.as_view()),
10-
path('products/cash/', views.ProductsCashView.as_view(), name='cash')
10+
path('products/cash/', views.ProductsCashView.as_view(), name='products-cash'),
11+
path('docs/', views.DocsView.as_view()),
12+
path('doc/id<int:pk>/', views.DocView.as_view()),
13+
path('doc/cash/', views.DocCashAddView.as_view(), name='doc-cash')
1114
]

api/views.py

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from django.http import JsonResponse
44
from django.views import View
5+
from django.views.generic import ListView, DetailView
56
from django.core.serializers import serialize#, JSONSerializer
67
from django.core.serializers.json import DjangoJSONEncoder
78
#from django.core.signing import Signer
@@ -10,11 +11,13 @@
1011
from django.views.decorators.csrf import csrf_exempt, csrf_protect, requires_csrf_token, ensure_csrf_cookie
1112
from django.utils.decorators import method_decorator
1213
from django.views.decorators.http import require_http_methods
14+
from django.core.paginator import Paginator
1315
from django.conf import settings
1416
from django.db.models import F
1517

1618
from users.models import User
17-
from refs.models import Product
19+
from refs.models import DocType, Product
20+
from core.models import Doc, Record
1821

1922

2023
@csrf_exempt
@@ -68,15 +71,11 @@ def url_logout(request):
6871
return JsonResponse({'success':"You're logged out."})
6972

7073

71-
#import asyncio
7274
class ProductsView(View):
7375
queryset = Product.objects.all()
7476

75-
#@method_decorator([login_required, csrf_protect, requires_csrf_token, ensure_csrf_cookie])
76-
#async def get(self, request, *args, **kwargs):
7777
@method_decorator([ensure_csrf_cookie])
7878
def get(self, request, *args, **kwargs):
79-
#await asyncio.sleep(1)
8079
data = serialize('json', self.queryset, fields=('id', 'article', 'name', 'cost', 'price', 'barcodes', 'currency'))
8180
return JsonResponse(data, safe=False)
8281

@@ -90,8 +89,83 @@ def get(self, request, *args, **kwargs):
9089
#qs = self.queryset.annotate(curr=F('currency__name')).prefetch_related('currency', 'barcodes').values('id', 'article', 'name', 'price', 'barcodes', 'curr')
9190
#json_data = json.dumps(list(qs), cls=DjangoJSONEncoder)
9291
#data = {it['id']:dict(filter(dflt, it.items())) for it in qs}
93-
data = {}
92+
data = []
9493
for it in self.queryset.prefetch_related('currency', 'barcodes', 'qrcodes', 'group', 'unit'):
95-
data[it.id] = {'article':it.article, 'name':it.name, 'price':it.price, 'barcodes':list(it.barcodes.values_list('id', flat=True)), 'qrcodes':list(it.qrcodes.values_list('id', flat=True)), 'currency':it.currency.name, 'group':it.group.name, 'unit':it.unit.label}
94+
data.append({'id':it.id, 'article':it.article, 'name':it.name, 'price':it.price, 'barcodes':list(it.barcodes.values_list('id', flat=True)), 'qrcodes':list(it.qrcodes.values_list('id', flat=True)), 'currency':it.currency.name if it.currency else '', 'grp':it.group.name if it.group else '', 'unit':it.unit.label if it.unit else ''})
9695
json_data = json.dumps(data, cls=DjangoJSONEncoder)
9796
return JsonResponse(json_data, safe=False)
97+
98+
99+
class DocView(DetailView):
100+
context_object_name = 'doc'
101+
queryset = Doc.objects.none()
102+
103+
@method_decorator([ensure_csrf_cookie])
104+
def get_context_data(self, **kwargs):
105+
context = super().get_context_data(**kwargs)
106+
context['doc_list'] = Doc.objects.all()
107+
return context
108+
109+
@method_decorator([ensure_csrf_cookie])
110+
def get(self, request, *args, **kwargs):
111+
data = serialize('json', self.queryset, fields=('id', 'article', 'name', 'cost', 'price', 'barcodes', 'currency'))
112+
return JsonResponse(data, safe=False)
113+
114+
115+
class DocCashAddView(DocView):
116+
context_object_name = 'doc-cash'
117+
118+
@method_decorator([ensure_csrf_cookie])
119+
def post(self, request, *args, **kwargs):
120+
try:
121+
data = json.loads(request.body)
122+
except json.decoder.JSONDecodeError as e:
123+
logging.error(e)
124+
return JsonResponse({'result':f'error: {e}'}, status=400)
125+
except Exception as e:
126+
logging.error(e)
127+
return JsonResponse({'result':f'error: {e}'}, status=500)
128+
sum_final = data.get('sum_final', 0)
129+
records = data.get('records', [])
130+
if records and sum_final:
131+
t, created = DocType.objects.get_or_create(alias='sale', defaults={'alias':'sale', 'name':'Sale'})
132+
doc = Doc(type=t, author=request.user)
133+
try:
134+
doc.save()
135+
except Exception as e:
136+
logging.error(e)
137+
return JsonResponse({'result':f'error: {e}'}, status=500)
138+
else:
139+
for r in records:
140+
try:
141+
p = Product.objects.get(pk=r['product'])
142+
except Exception as e:
143+
logging.error([r, e])
144+
else:
145+
record = Record(count=r['count'], cost=p.cost, price=r['price'], doc=doc, currency=p.currency, product=p)
146+
try:
147+
record.save()
148+
except Exception as e:
149+
logging.error([r, record, e])
150+
return JsonResponse({'result':'success', 'doc':f'{doc.id}'})
151+
152+
153+
class DocsView(ListView):
154+
paginate_by = 10
155+
model = Doc
156+
context_object_name = 'docs'
157+
queryset = Doc.objects.all()
158+
159+
@method_decorator([ensure_csrf_cookie])
160+
def get(self, request, *args, **kwargs):
161+
if request.GET:
162+
self.queryset = self.queryset.filter(**request.GET)
163+
paginator = Paginator(self.queryset, self.paginate_by)
164+
page_number = request.GET.get('page')
165+
page_obj = paginator.get_page(page_number)
166+
logging.debug(page_obj.object_list)
167+
#data = {'page':page_number, 'docs':[self.queryset]}
168+
#json_data = json.dumps(data, cls=DjangoJSONEncoder)
169+
json_data = serialize('json', page_obj, fields=('id', 'created_at', 'registered_at', 'owner', 'contractor', 'type', 'tax', 'sale_point', 'sum_final', 'author'))
170+
logging.debug(json_data)
171+
return JsonResponse(json_data, safe=False)

locale/ru_RU/LC_MESSAGES/django.po

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,9 @@ msgstr "Пользователи"
506506
msgid "load from XLS file"
507507
msgstr "загрузить из файла XLS"
508508

509+
msgid "load from XLS file with check (slow)"
510+
msgstr "загрузить из файла XLS с проверкой (медленно)"
511+
509512
msgid "export to XLS file"
510513
msgstr "экспорт в файл XLS"
511514

refs/admin.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ class ProductAdmin(CustomModelAdmin):
698698
list_select_related = ('tax', 'model', 'group')
699699
list_filter = (ProductGroupFilter, ProductManufacturerFilter, ProductModelFilter, TaxFilter)
700700
autocomplete_fields = ('tax', 'model', 'group', 'barcodes', 'qrcodes')
701-
actions = ('from_xls', 'to_xls', 'price_to_xls', 'barcode_to_svg', 'fix_barcodes', 'reset_cached')
701+
actions = ('from_xls_with_check', 'from_xls', 'to_xls', 'price_to_xls', 'barcode_to_svg', 'fix_barcodes', 'reset_cached')
702702

703703
#class Media:
704704
#js = ['admin/js/autocomplete.js', 'admin/js/vendor/select2/select2.full.js']
@@ -859,6 +859,122 @@ def reset_cached(self, request, queryset, **kwargs):
859859
self.__objs__ = {}
860860
reset_cached.short_description = f'↻💦{_("reset cached values")}'
861861

862+
def from_xls_with_check(self, request, queryset, **kwargs):
863+
from barcode import EAN13
864+
from transliterate import slugify
865+
from openpyxl import load_workbook
866+
form = None
867+
if 'apply' in request.POST:
868+
self.logi('💡', request.FILES)
869+
form = UploadFileForm(request.POST, request.FILES)
870+
if form.is_valid():
871+
msg_err = ''
872+
count_created = 0
873+
file = form.cleaned_data['file']
874+
if file:
875+
units, groups, doc_income = {}, {}, None
876+
timestamp = int(time.time())
877+
wb = load_workbook(file)
878+
for sheetname in wb.sheetnames:
879+
self.logi('💡SHEET NAME', sheetname)
880+
ws = wb[sheetname]
881+
list_rows = list(ws.rows)
882+
prefix_cells = list_rows[0]
883+
self.logi(prefix_cells)
884+
row_index = 0
885+
for row in list_rows[1:]:
886+
if msg_err and msg_err[-1] != '\n':
887+
msg_err += '\n'
888+
row_index += 1
889+
v = list(row)
890+
try:
891+
p_group, p_code, p_name, p_article, p_unit, p_price, p_cost, p_barcode, p_count = [i.value for i in v]
892+
except Exception as e:
893+
self.loge(e)
894+
msg_err += f'ROW {row_index}: {e}'
895+
else:
896+
if Product.objects.filter(article=p_article).exists():
897+
self.logi('PRODUCT', p_article, p_name, 'EXISTS')
898+
else:
899+
product_kwargs = {'article':p_article if p_article else f'{timestamp+row_index}', 'name':p_name, 'cost':p_cost if isinstance(p_cost, (int, float)) else 0, 'price':p_price if isinstance(p_price, (int, float)) else 0}
900+
if p_unit:
901+
if p_unit not in units:
902+
condition_unit = Q(label=p_unit) | Q(label__icontains=p_unit)
903+
condition_unit |= Q(name=p_unit) | Q(name__icontains=p_unit)
904+
unit = Unit.objects.filter(condition_unit).first()
905+
if not unit:
906+
unit = Unit(label=p_unit, name=p_unit)
907+
try:
908+
unit.save()
909+
except Exception as e:
910+
self.loge(e)
911+
unit = None
912+
if unit:
913+
units[p_unit] = unit
914+
if p_unit not in units:
915+
product_kwargs['unit'] = units[p_unit]
916+
if p_group:
917+
if p_group not in groups:
918+
groups[p_group], created_group = ProductGroup.objects.get_or_create(name__icontains=p_group, defaults={'name':p_group})
919+
product_kwargs['group'] = groups[p_group]
920+
p = Product(**product_kwargs)
921+
try:
922+
p.save()
923+
except Exception as e:
924+
self.loge(e)
925+
msg_err += f'; {e}'
926+
continue
927+
count_created += 1
928+
time.sleep(.01)#FOR STRONG NEXT EAN13 GENERATION
929+
barcode = f'{p_barcode}' if p_barcode is not None else EAN13(f'{round(time.time()*1000)}').ean
930+
if barcode:
931+
b, created = BarCode.objects.get_or_create(id=barcode)
932+
if b:
933+
p.barcodes.add(b)
934+
if p_count:
935+
if not doc_income:
936+
t, created = DocType.objects.get_or_create(alias='balance', defaults={'alias':'balance', 'name':'Balance'})
937+
doc_income = get_model('core.Doc')(type=t, author=request.user)
938+
try:
939+
doc_income.save()
940+
except Exception as e:
941+
self.loge(e)
942+
msg_err += f'; {e}'
943+
if doc_income:
944+
r = get_model('core.Record')(count=p_count, cost=p.cost, price=p.price, doc=doc_income, product=p)
945+
try:
946+
r.save()
947+
except Exception as e:
948+
self.loge(e)
949+
msg_err += f'; {e}'
950+
else:
951+
try:
952+
get_model('core.Register')(rec=r).save()
953+
except Exception as e:
954+
self.loge(e)
955+
msg_err += f'; {e}'
956+
self.message_user(request, f'🆗 {file.name} ✏️ FILE SIZE={file.size} ✏️ CREATED={count_created}; {msg_err}', messages.SUCCESS)
957+
return HttpResponseRedirect(request.get_full_path())
958+
if not form:
959+
form = UploadFileForm(initial={'_selected_action': request.POST.getlist(admin.helpers.ACTION_CHECKBOX_NAME)})
960+
m = queryset.model._meta
961+
context = {}
962+
context['items'] = []
963+
context['form'] = form
964+
context['title'] = _('File')
965+
context['current_action'] = sys._getframe().f_code.co_name
966+
context['subtitle'] = 'admin_select_file_form'
967+
context['site_title'] = queryset.model._meta.verbose_name
968+
context['is_popup'] = True
969+
context['is_nav_sidebar_enabled'] = True
970+
context['site_header'] = _('Admin panel')
971+
context['has_permission'] = True
972+
context['site_url'] = reverse('admin:{}_{}_changelist'.format(m.app_label, m.model_name))
973+
context['available_apps'] = (m.app_label,)
974+
context['app_label'] = m.app_label
975+
return render(request, 'admin_select_file_form.html', context)
976+
from_xls_with_check.short_description = f'📍⚔📉{_("load from XLS file with check (slow)")}📈'
977+
862978
def from_xls(self, request, queryset, **kwargs):
863979
from barcode import EAN13
864980
from transliterate import slugify

0 commit comments

Comments
 (0)