提交 ebec5282 编写于 作者: P Pzqqt

Import project files

上级 972cb9d5
"""
ASGI config for PPWuLiu project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
"""
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import websocket.routing as websocket_routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'PPWuLiu.settings')
# application = get_asgi_application()
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
websocket_routing.websocket_urlpatterns
)
),
})
"""
Django settings for PPWuLiu project.
Generated by 'django-admin startproject' using Django 3.1.2.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
import os
from pathlib import Path
from django.contrib.messages import constants
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# 创建日志的路径
LOG_PATH = os.path.join(BASE_DIR, "logs")
if not os.path.isdir(LOG_PATH):
if os.path.isfile(LOG_PATH):
os.remove(LOG_PATH)
os.makedirs(LOG_PATH)
# 启用Django Debug Toolbar
ENABLE_DJANGO_TOOLBAR = False
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv("DJANGO_TMS_SECRET_KEY")
PBKDF2_ITERATIONS = 3
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
# 'channels',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 'rest_framework',
# 'django_filters',
'wuliu.apps.WuliuConfig',
'utils',
]
if DEBUG:
try:
import django_extensions
INSTALLED_APPS.append('django_extensions')
# print SQL queries in shell_plus
SHELL_PLUS_PRINT_SQL = True
except ImportError:
pass
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# Pyinstrument, 暂时不用, 因为Django Debug Toolbar更好用更强大
# if DEBUG:
# try:
# import pyinstrument
# MIDDLEWARE.append('pyinstrument.middleware.ProfilerMiddleware')
# except ImportError:
# pass
# Django Debug Toolbar
if ENABLE_DJANGO_TOOLBAR:
# debug_toolbar依赖于django.contrib.staticfiles
if DEBUG and 'django.contrib.staticfiles' in INSTALLED_APPS:
try:
import debug_toolbar
INSTALLED_APPS.append('debug_toolbar')
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
# Internal IPs
INTERNAL_IPS = [
'127.0.0.1',
]
except ImportError:
pass
ROOT_URLCONF = 'PPWuLiu.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'wuliu.processor.pros'
],
},
},
]
WSGI_APPLICATION = 'PPWuLiu.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': os.getenv("DJANGO_TMS_MYSQL_DATABASE_NAME"),
'USER': os.getenv("DJANGO_TMS_MYSQL_USER"),
'PASSWORD': os.getenv("DJANGO_TMS_MYSQL_PASSWORD"),
'HOST': os.getenv("DJANGO_TMS_MYSQL_HOST"),
'PORT': os.getenv("DJANGO_TMS_MYSQL_PORT"),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static")
]
# Custom
CACHES = {
'default': {
# 基于本地内存的缓存
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
# 关闭浏览器使会话立即过期
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
MESSAGE_TAGS = {
constants.ERROR: "danger",
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
},
# Unused
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '%s/log.txt' % LOG_PATH,
'encoding': 'utf-8',
},
'request_exceptions': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '%s/request_exceptions.txt' % LOG_PATH,
'encoding': 'utf-8',
},
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
},
'wuliu.apps': {
'handlers': ['request_exceptions'],
'level': 'INFO',
'propagate': True,
},
'utils.common.expire_lru_cache': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
},
}
'''
# channels相关配置, 暂时不用
ASGI_APPLICATION = "PPWuLiu.asgi.application"
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [('127.0.0.1', 6379)],
},
},
}
'''
"""PPWuLiu URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
# from django.views.generic.base import RedirectView
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
# path('', RedirectView.as_view(url='tms/login')),
path('tms/', include('wuliu.urls')),
path('utils/', include('utils.urls')),
]
if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns.append(path('__debug__/', include(debug_toolbar.urls)))
"""
WSGI config for PPWuLiu project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'PPWuLiu.settings')
application = get_wsgi_application()
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'PPWuLiu.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
/* 单行代码 */
code {
color: #2d85ca;
padding: 2px 4px;
font-size: 90%;
background-color: #f0f3f3;
border-radius: 4px;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}
/* 必填表单项label字符串前加红色星号 */
.label-required:before {
content: "* ";
color: #eb4434;
}
/* 限制textarea的最大高度, 并禁止调整宽度 */
textarea {
resize: vertical;
min-height: 60px;
}
/* 运单信息中的总价 */
.total-fee:before {
content: "¥ ";
}
.total-fee {
font-size: 16px;
color: #eb4434;
font-weight: 700;
}
/* 阻止表格内容折行 */
.table th, .table td {
white-space: nowrap;
}
/* 适当调整form label上下填充 */
form label {
margin-top: .25rem;
margin-bottom: .25rem;
}
/* 适当降低legend字体大小, 并调整上下边距 */
legend {
margin-top: .15rem;
margin-bottom: .25rem;
font-size: 1.25rem;
}
/* 子元素对齐 */
.child-flex {
display: flex;
}
.child-flex-xc {
justify-content: center;
}
.child-flex-xr {
justify-content: right;
}
.child-flex-yc {
align-items: center;
}
/* 对RemixIcon的一些自定义调整 */
.ri-c {
line-height: 1; /* 固定行高 */
vertical-align: text-bottom; /* 向底端对齐 */
font-size: 125%; /* 适当增大 (5/4) */
}
.ri-c span {
font-size: 80%; /* 缩小到与icon大小相同 (4/5) */
vertical-align: text-top;
padding-left: .25rem;
}
/* 用户头像 */
.nav-user-icon {
margin-right: .25rem;
width: 2.125rem;
}
/* 对用户权限树元素的一些排版调整 */
.tree-pl {
padding-left: 2.75rem;
}
.tree-icon-pl {
padding-right: 1.75rem;
}
.permission-padding-fix {
padding-left: 1.15rem
}
.custom-checkbox-label-fix::before {
top: .125rem;
}
.custom-checkbox-label-fix::after {
top: .125rem;
}
function alert_dialog(text) {
new duDialog("错误", text, {okText: "确认"});
}
function confirm_dialog(title, text, callbacks) {
new duDialog(
title, text, {
buttons: duDialog.OK_CANCEL,
cancelText: "取消",
okText: "确认",
callbacks: callbacks,
},
);
}
function mdtoast_success(text) {
mdtoast.success(text);
}
function mdtoast_error(text) {
mdtoast.error(text);
}
function find_datatable_rows_all(datatable_obj) {
return datatable_obj.rows().nodes().filter(function(row) {
return $(row).find("input:checkbox");
}).map($);
}
function find_datatable_rows_clicked(datatable_obj) {
return datatable_obj.rows().nodes().filter(function(row) {
return $(row).find("input:checkbox").is(":checked");
}).map($);
}
$.extend({
StandardPost: function(url, args){
let form = $("<form method='post' hidden></form>");
let input;
$(document.body).append(form);
form.attr({"action": url});
$.each(args, function(key, value) {
if ($.isArray(value)) {
input = $("<select type='hidden' multiple></select>");
input.attr({"name": key});
value.forEach(function(value_) {
input.append("<option value=" + value_ + "></option>")
});
} else {
input = $("<input type='hidden'>");
input.attr({"name": key});
}
input.val(value);
form.append(input);
});
form.append(
$("<input type='hidden' name='csrfmiddlewaretoken' value='" + $("[name='csrfmiddlewaretoken']").val() + "'>")
);
form.submit();
form.remove();
}
});
$(document).ready(function() {
duDatepicker(".md-date-picker", {format: 'yyyy-mm-dd', auto: true, i18n: 'zh', maxDate: 'today'});
mdtimepicker(".md-time-picker", {is24hour: true});
$(".md-date-picker, .md-time-picker").removeAttr("readonly");
$(".select2").select2({
theme: "bootstrap4",
dropdownCssClass: "text-sm", // 与body缩放匹配
width: "style", // 解决越界问题
minimumResultsForSearch: 5, // 可选项少于5项则禁用搜索框
});
$(".multiple-select").multipleSelect({
placeholder: "未指定",
formatSelectAll: function() {return "[全选]"},
formatAllSelected: function() {return "全部"},
formatCountSelected: function(count, total) {return "已选择" + count + ""},
formatNoMatchesFound: function() {return "未选择"},
});
$('[data-widget="pushmenu"]').click(function() {
Cookies.set("ui_enable_sidebar_collapse", Cookies.get("ui_enable_sidebar_collapse") !== "true");
});
// 全局禁用input获得焦点时按回车键提交表单, 除非该元素有"data-allow_enter_submit"属性
$(".content-wrapper form input").keypress(function(e) {
if (e.keyCode === 13 && $(this).attr("data-allow_enter_submit") === undefined) {
e.preventDefault();
}
});
});
from ._common import (
UnescapedDjangoJSONEncoder, UnescapedJsonResponse, SortableModelChoiceField,
traceback_log, traceback_and_detail_log,
validate_comma_separated_integer_list_and_split, model_to_dict_, del_session_item
)
from .expire_lru_cache import ExpireLruCache
from functools import partial
from itertools import chain
from collections import UserList
import logging
import traceback
from django import forms
from django.db.models import Model
from django.core.validators import validate_comma_separated_integer_list
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.fields.related import ForeignKey
from django.http import JsonResponse
from django.utils import timezone
class UnescapedDjangoJSONEncoder(DjangoJSONEncoder):
""" 自定义的JSON编码器, 强制ensure_ascii为False, 避免中文字符被编码为乱码 """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 强制ensure_ascii为False
self.ensure_ascii = False
UnescapedJsonResponse = partial(JsonResponse, encoder=UnescapedDjangoJSONEncoder)
class SortableModelChoiceField(forms.ModelChoiceField):
"""
为ModelChoiceField的choices进行排序是件很麻烦的事
尽管我们可以对queryset属性使用`order_by`进行排序
但是还需要考虑对数据库的优化(尽可能避免explain中出现`using filesort`)
因此, 我们在ModelChoiceIterator中添加一个额外可选的属性, 以允许在遍历choices时对其进行排序
这是在应用层的排序, 意在减少数据库的压力
"""
class _ModelChoiceIterator(forms.models.ModelChoiceIterator):
class _FakeQuerySet(UserList):
_prefetch_related_lookups = ()
def iterator(self):
yield from self
def __iter__(self):
sort_key = self.field.sort_key
if sort_key is not None:
# sorted之后(立即执行数据库查询), _prefetch_related_lookups就没有意义了
self.queryset = self._FakeQuerySet(sorted(self.queryset, key=sort_key))
return super().__iter__()
iterator = _ModelChoiceIterator
def __init__(self, queryset, **kwargs):
super().__init__(queryset, **kwargs)
self.sort_key = kwargs.get("sort_key", None)
def multi_lines_log(logger: logging.Logger, string: str, level=logging.INFO):
""" 记录多行日志 """
for line in string.splitlines():
logger.log(level, line)
def traceback_log(logger: logging.Logger, level=logging.ERROR):
""" 记录异常栈 """
multi_lines_log(logger=logger, string=traceback.format_exc(), level=level)
def traceback_and_detail_log(request, logger: logging.Logger, level=logging.ERROR):
""" 记录异常栈和其他一些详细信息 """
logger.log(level, "=" * 100)
logger.log(level, "Exception:")
logger.log(level, "Time: %s" % timezone.make_naive(timezone.now()).strftime("%Y-%m-%d %H:%M:%S"))
logger.log(level, "Url: %s" % request.path)
logger.log(level, "Method: %s" % request.method)
logger.log(level, "Cookies: %s" % request.COOKIES)
logger.log(level, "Session: %s" % dict(request.session.items()))
if request.method == "POST":
logger.log(level, "Post data: %s" % request.POST.dict())
logger.log(level, "")
traceback_log(logger=logger, level=level)
logger.log(level, "=" * 100)
def validate_comma_separated_integer_list_and_split(string: str, auto_strip=True) -> list:
""" 判断字符串是否是一个以逗号分隔的数字列表
如果是, 则自动进行分割并返回列表; 如果不是, 则抛出ValidationError异常
:param string: 要解析的字符串
:param auto_strip: 为True时则提前对string进行strip(默认)
:return: list
"""
if auto_strip:
string = string.strip()
validate_comma_separated_integer_list(string)
return [int(x) for x in string.split(',')]
def model_to_dict_(instance: Model) -> dict:
""" Django有一个内置的django.forms.models.model_to_dict方法(以下简称原model_to_dict方法)
可以方便地把模型转为字典, 但是有一个坑, 被标记为不可编辑(editable为False)的字段不会包含在输出的字典中
原model_to_dict方法仅在初始化ModelForm时被使用, 为了安全起见, 这样做无可厚非
但是我们想要的"模型转为字典"的方法应该包含模型的所有字段
所以我们参考原model_to_dict方法编写了新的model_to_dict_方法
比起原model_to_dict方法缺少了fields和exclude参数, 因为我们暂时不需要
"""
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
# 对于一对一和多对一外键, 返回外键模型对象 (多对多外键会在else子句中合适地处理)
# 注: 由于ForeignKey的attname属性值为"字段名_id", 所以调用value_from_object方法的话, 返回的是外键对象的id
if isinstance(f, ForeignKey):
data[f.name] = getattr(instance, f.name, None)
else:
data[f.name] = f.value_from_object(instance)
return data
def del_session_item(request, *items):
""" 从request会话中删除键值 """
for item in items:
request.session.pop(item, None)
from functools import wraps
from collections import namedtuple
import threading
import logging
from django.utils import timezone
_logger = logging.getLogger(__name__)
ValueAndType = namedtuple("ValueAndType", ["value", "type"])
class ExpireLruCache:
""" 一个简易的支持过期功能的缓存装饰器, 在该项目中, 用于装饰那些频繁访问数据库且不要求时效性的方法
和标准库中的functools.lru_cache一样, 被装饰的函数不能使用不可哈希的参数
参数expire_time为过期时间, 必须是datetime.timedelta类型, 默认为3分钟
参数enable_log为真值时, 则每次取出缓存时记录日志[方法名, 参数, 获取缓存的次数]
"""
def __init__(self, expire_time=timezone.timedelta(minutes=3), enable_log=False):
assert isinstance(expire_time, timezone.timedelta), "expire_time参数必须为datetime.timedelta类型"
self.expire_time = expire_time
self.enable_log = enable_log
self._dic = {}
self._lock = threading.RLock()
@staticmethod
def _print_log(func, args, kwargs, count):
_logger.info("%s: function name: %s, args: (%s), get cache count: %s" % (
__name__,
func.__name__,
", ".join([*[str(arg) for arg in args], *["%s=%s" % (k, v) for k, v in kwargs]]),
count,
))
def __call__(self, func):
@wraps(func)
def _func(*args, **kwargs):
# 被装饰函数调用时的每个参数都必须是可哈希的
hashable_args = tuple((ValueAndType(value=arg, type=type(arg)) for arg in args))
hashable_kwargs = frozenset((
(k, ValueAndType(value=v, type=type(v))) for k, v in kwargs.items()
))
key_ = hash((func, hashable_args, hashable_kwargs))
with self._lock:
if key_ in self._dic.keys():
if self._dic[key_]["latest_update_time"] + self.expire_time > timezone.now():
self._dic[key_]["count"] += 1
if self.enable_log:
self._print_log(func, args, kwargs, self._dic[key_]["count"])
return self._dic[key_]["result"]
result = func(*args, **kwargs)
with self._lock:
self._dic[key_] = {
"result": result,
"latest_update_time": timezone.now(),
"count": 0,
}
return result
return _func
'''
# ExpireLruCache的函数装饰器版, 效果与类装饰器版完全一致, 但可读性不及类装饰器版, 仅供参考
def ExpireLruCache(expire_time=timezone.timedelta(minutes=3), enable_log=False):
_dic = {}
_lock = threading.RLock()
def _print_log(func, args, kwargs, count):
_logger.info("%s: function name: %s, args: (%s), get cache count: %s" % (
__name__,
func.__name__,
", ".join([*[str(arg) for arg in args], *["%s=%s" % (k, v) for k, v in kwargs]]),
count,
))
def wrapper(func):
@wraps(func)
def _func(*args, **kwargs):
nonlocal _dic
hashable_args = tuple((ValueAndType(value=arg, type=type(arg)) for arg in args))
hashable_kwargs = frozenset((
(k, ValueAndType(value=v, type=type(v))) for k, v in kwargs.items()
))
key_ = hash((func, hashable_args, hashable_kwargs))
with _lock:
if key_ in _dic.keys() and _dic[key_]["latest_update_time"] + expire_time > timezone.now():
_dic[key_]["count"] += 1
if enable_log:
_print_log(func, args, kwargs, _dic[key_]["count"])
return _dic[key_]["result"]
result = func(*args, **kwargs)
with _lock:
_dic[key_] = {
"result": result,
"latest_update_time": timezone.now(),
"count": 0,
}
return result
return _func
return wrapper
'''
import itertools
from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, Side
from openpyxl.writer.excel import save_virtual_workbook
_BD = Side(style='thin', color="000000")
_CENTER = Alignment(
horizontal="center",
vertical="center",
)
_RIGHT = Alignment(
horizontal="right",
vertical="center",
)
_FONT_TITLE = Font(name='黑体', size=18, bold=True)
_FONT_HEADER = Font(name='黑体', size=12, bold=True)
_FONT_VALUE = Font(name='等线', size=12, bold=False)
def add_all_border(cell_):
""" 单元格添加所有细框线 """
cell_.border = Border(left=_BD, top=_BD, right=_BD, bottom=_BD)
def cell_center(cell_):
""" 单元格水平居中&垂直居中 """
cell_.alignment = _CENTER
def cell_right(cell_):
""" 单元格水平居右&垂直居中 """
cell_.alignment = _RIGHT
def gen_workbook(title, thead, trs):
wb = Workbook()
ws = wb.active
ws.append([title, ])
ws.append(thead)
for tr in trs:
ws.append(tr)
# 合并首行(标题)
ws.merge_cells("A1:%s1" % ws[2][-1].column_letter)
# 给所有单元格设置居中+所有边框格式
for cell in itertools.chain.from_iterable(ws.rows):
cell_center(cell)
add_all_border(cell)
# 给每行单元格设置字体
ws['A1'].font = _FONT_TITLE
for cell in ws[2]:
cell.font = _FONT_HEADER
for cell in itertools.chain.from_iterable(list(ws.rows)[2:]):
cell.font = _FONT_VALUE
# 适应列宽
for col in ws.columns:
max_len_set = set()
for cell in col:
cell_len = 0
if not cell.value:
continue
for char in cell.value:
if ord(char) <= 256:
cell_len += 1.3
else:
cell_len += 2.6
max_len_set.add(cell_len)
ws.column_dimensions[col[1].column_letter].width = max(max_len_set)
return save_virtual_workbook(wb)
def test():
title_ = "Title"
thead_ = ["Head 1", "Head 2", "Head 3"]
trs_ = [["Value 1", "Value 2", "Value 3"] for _ in range(9)]
return gen_workbook(title_, thead_, trs_)
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
# Unused
@register.simple_tag()
def csrfmiddlewaretoken_js():
""" 用于在ajax时添加csrf_token数据, 注意结尾不再需要加逗号
示例:
$.post(
"post/url",
{
"key": "value",
// "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(),
// 替换为:
{% csrfmiddlewaretoken_js %}
},
...
);
"""
return mark_safe('"csrfmiddlewaretoken": $("[name=\'csrfmiddlewaretoken\']").val(),')
register.filter(name="max")(lambda value: max(value))
register.filter(name="abs")(lambda value: abs(value))
register.filter(name="sum")(lambda value: sum(value))
register.filter(name="subtract")(lambda value, arg: int(value) - int(arg))
register.filter(name="bool")(lambda value: value and 1 or 0)
from django.urls import path
from . import views
app_name = "utils"
urlpatterns = [
path("export_excel", views.export_excel, name="export_excel"),
]
import json
from django.conf import settings
from django.http import HttpResponseBadRequest, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from .export_excel import gen_workbook
@require_POST
@csrf_exempt
def export_excel(request):
""" 导出excel表格
前端必须用表单"显式"提交(可以用隐藏表单), 不能用ajax, 否则无法触发下载
"""
table_title = request.POST.get("table_title")
table_header = request.POST.get("table_header")
table_rows = request.POST.get("table_rows")
if not (table_title and table_header and table_rows):
return HttpResponseBadRequest()
try:
table_header = json.loads(table_header)
table_rows = json.loads(table_rows)
except json.decoder.JSONDecodeError:
if settings.DEBUG:
raise
return HttpResponseBadRequest()
# 不能用FileResponse, 也不能用StreamingHttpResponse, 很奇怪
response = HttpResponse(gen_workbook(table_title, table_header, table_rows))
response["Content-Type"] = "application/octet-stream"
# 此处必须编码为ansi, 否则filename有中文的话会乱码
response["Content-Disposition"] = ('attachment; filename="%s.xlsx"' % table_title).encode("ansi")
return response
from django.contrib import admin
from . import models
class PermissionGroupAdmin(admin.ModelAdmin):
list_display = ["print_name", "name", "tree_str"]
list_filter = ["father", ]
fields = ["name", "print_name", "father"]
class PermissionAdmin(admin.ModelAdmin):
list_display = ["print_name", "name", "tree_str"]
list_filter = ["father", ]
fields = ["name", "print_name", "father"]
class DepartmentAdmin(admin.ModelAdmin):
list_display = [
"name", "tree_str", "unit_price", "is_branch_group", "is_branch",
"enable_src", "enable_dst", "enable_cargo_price"
]
list_filter = ["enable_src", "enable_dst", "enable_cargo_price"]
class UserAdmin(admin.ModelAdmin):
readonly_fields = ["create_time"]
list_display = ["name", "department", "create_time", "administrator", "enabled"]
list_filter = ["enabled"]
class CustomerAdmin(admin.ModelAdmin):
readonly_fields = ["score", "create_time"]
list_display = ["name", "phone", "bank_name", "score", "is_vip", "enabled"]
list_filter = ["is_vip", "enabled", "create_time"]
class WaybillAdmin(admin.ModelAdmin):
fieldsets = [
("基本信息", {"fields": (
"src_department", "dst_department", "status", "cargo_price_status",
"create_time", "arrival_time", "sign_for_time",
)}),
("发货人信息", {"fields": (
"src_customer", "src_customer_name", "src_customer_phone",
"src_customer_credential_num", "src_customer_address",
)}),
("收货人信息", {"fields": (
"dst_customer", "dst_customer_name", "dst_customer_phone",
"dst_customer_credential_num", "dst_customer_address",
)}),
("货物信息", {"fields": (
"cargo_name", "cargo_num",
"cargo_volume", "cargo_weight", "cargo_price", "cargo_handling_fee",
)}),
("运费", {"fields": ("fee", "fee_type")}),
("其他", {"fields": (
"customer_remark", "company_remark", "drop_reason",
)}),
]
readonly_fields = [
"src_department", "dst_department", "cargo_price_status",
"create_time", "arrival_time", "sign_for_time",
"src_customer", "dst_customer",
"cargo_num", "status", "drop_reason"
]
list_display = [
"get_full_id", "src_department", "dst_department", "fee", "fee_type",
"create_time", "status", "cargo_price_status"
]
list_filter = ["create_time"]
class TruckAdmin(admin.ModelAdmin):
readonly_fields = ["create_time"]
list_display = ["number_plate", "driver_name", "driver_phone", "create_time", "enabled"]
list_filter = ["enabled", "create_time"]
class TransportOutAdmin(admin.ModelAdmin):
readonly_fields = [
"create_time", "start_time", "end_time",
"src_department", "dst_department", "status", "waybills"
]
list_display = [
"get_full_id", "truck", "driver_name", "src_department", "dst_department",
"start_time", "end_time", "status"
]
list_filter = ["create_time", "start_time", "end_time"]
admin.site.register(models.Settings)
admin.site.register(models.Department, DepartmentAdmin)
admin.site.register(models.User, UserAdmin)
admin.site.register(models.Customer, CustomerAdmin)
admin.site.register(models.Waybill, WaybillAdmin)
# admin.site.register(models.WaybillRouting)
admin.site.register(models.Truck, TruckAdmin)
admin.site.register(models.TransportOut, TransportOutAdmin)
admin.site.register(models.Permission, PermissionAdmin)
admin.site.register(models.PermissionGroup, PermissionGroupAdmin)
此差异已折叠。
import logging
from django.apps import AppConfig
from django.core.signals import got_request_exception
from django.dispatch import receiver
from utils.common import traceback_and_detail_log
class WuliuConfig(AppConfig):
name = "wuliu"
verbose_name = "物流运输管理系统"
default_auto_field = "django.db.models.AutoField"
_request_exception_logger = logging.getLogger(__name__)
@receiver(got_request_exception)
def _(sender, request, **kwargs):
traceback_and_detail_log(request, _request_exception_logger)
from functools import wraps
from django.shortcuts import redirect
from django.http import Http404, HttpResponseForbidden
from django.utils import timezone
from .models import (
User, Waybill, TransportOut, DepartmentPayment, CargoPricePayment, Permission, PermissionGroup,
_get_global_settings,
)
from utils.common import ExpireLruCache, model_to_dict_
expire_lru_cache_three_hours = ExpireLruCache(expire_time=timezone.timedelta(hours=3))
expire_lru_cache_one_minute = ExpireLruCache(expire_time=timezone.timedelta(minutes=1))
get_global_settings = expire_lru_cache_three_hours(_get_global_settings)
@expire_lru_cache_one_minute
def _get_logged_user_by_id(user_id: int) -> User:
""" 根据用户名返回用户模型对象 """
return User.objects.get(id=user_id)
def get_logged_user(request) -> User:
""" 获取已登录的用户对象 """
return _get_logged_user_by_id(request.session["user"]["id"])
def get_logged_user_type(request) -> User.Types:
""" 获取已登录的用户的用户类型 """
return get_logged_user(request).get_type
@expire_lru_cache_one_minute
def _get_user_permissions(user: User) -> set:
""" 获取用户拥有的权限, 注意该方法返回的是集合而不是QuerySet """
return set(user.permission.all().values_list("name", flat=True))
def is_logged_user_has_perm(request, perm_name: str) -> bool:
""" 检查已登录用户是否具有perm_name权限
:return: True或False
"""
if not perm_name:
return True
return perm_name in _get_user_permissions(get_logged_user(request))
def is_logged_user_is_goods_yard(request) -> bool:
""" 判断已登录的用户是否属于货场 """
return get_logged_user_type(request) == User.Types.GoodsYard
def _gen_permission_tree_list(root_pg_=PermissionGroup.objects.get(father__isnull=True)) -> list:
""" 根据所有的权限组和权限的层级结构生成列表, 用于前端渲染 """
tree_list = []
for pg in PermissionGroup.objects.filter(father=root_pg_):
tree_list.append({
"id": pg.id, "name": pg.name, "print_name": pg.print_name, "children": _gen_permission_tree_list(pg)
})
for p in Permission.objects.filter(father=root_pg_):
tree_list.append({
"id": p.id, "name": p.name, "print_name": p.print_name,
})
return tree_list
PERMISSION_TREE_LIST = _gen_permission_tree_list()
def login_required(raise_404=False):
""" 自定义装饰器, 用于装饰路由方法
若用户未登录, 则跳转到登录页面
raise_404为True时, 则跳转到404页面
"""
def _login_required(func):
@wraps(func)
def login_check(request, *args, **kwargs):
if not request.session.get("user"):
if raise_404:
raise Http404
return redirect("wuliu:login")
return func(request, *args, **kwargs)
return login_check
return _login_required
def check_permission(perm_name: str):
""" 自定义装饰器, 用于在请求前检查用户是否具有perm_name权限
若无perm_name权限则跳转至403页面
"""
def _check_permission(func):
@wraps(func)
def perm_check(request, *args, **kwargs):
if perm_name and not is_logged_user_has_perm(request, perm_name):
return HttpResponseForbidden()
return func(request, *args, **kwargs)
return perm_check
return _check_permission
def check_administrator(func):
""" 自定义装饰器, 用于在请求前检查用户是否为管理员
若不是管理员则跳转至403页面
"""
@wraps(func)
def admin_check(request, *args, **kwargs):
if not get_logged_user(request).administrator:
return HttpResponseForbidden()
return func(request, *args, **kwargs)
return admin_check
def waybill_to_dict(waybill_obj: Waybill) -> dict:
""" 将Waybill对象转为字典 """
waybill_dic = model_to_dict_(waybill_obj)
waybill_dic["id_"] = waybill_obj.get_full_id
waybill_dic["fee_type_id"] = waybill_dic["fee_type"]
waybill_dic["fee_type"] = waybill_obj.get_fee_type_display()
waybill_dic["status_id"] = waybill_dic["status"]
waybill_dic["status"] = waybill_obj.get_status_display()
if waybill_obj.return_waybill is not None:
waybill_dic["return_waybill"] = waybill_to_dict(waybill_obj.return_waybill)
else:
waybill_dic["return_waybill"] = None
return waybill_dic
def transport_out_to_dict(transport_out_obj: TransportOut) -> dict:
""" 将TransportOut对象转为字典 """
to_dic = model_to_dict_(transport_out_obj)
to_dic["id_"] = transport_out_obj.get_full_id
to_dic["status_id"] = to_dic["status"]
to_dic["status"] = transport_out_obj.get_status_display()
to_dic.update(transport_out_obj.gen_waybills_info())
return to_dic
def department_payment_to_dict(department_payment_obj: DepartmentPayment) -> dict:
""" 将DepartmentPayment对象转为字典 """
dp_dic = model_to_dict_(department_payment_obj)
dp_dic["id_"] = department_payment_obj.get_full_id
dp_dic["status_id"] = dp_dic["status"]
dp_dic["status"] = department_payment_obj.get_status_display()
total_fee_dic = department_payment_obj.gen_total_fee()
dp_dic["total_fee_now"] = total_fee_dic["fee_now"]
dp_dic["total_fee_sign_for"] = total_fee_dic["fee_sign_for"]
dp_dic["total_cargo_price"] = total_fee_dic["cargo_price"]
dp_dic["final_total_fee"] = sum(total_fee_dic.values())
return dp_dic
def cargo_price_payment_to_dict(cargo_price_payment_obj: CargoPricePayment) -> dict:
""" 将CargoPricePayment对象转为字典 """
cpp_dic = model_to_dict_(cargo_price_payment_obj)
cpp_dic["id_"] = cargo_price_payment_obj.get_full_id
cpp_dic["status_id"] = cpp_dic["status"]
cpp_dic["status"] = cargo_price_payment_obj.get_status_display()
total_fee_dic = cargo_price_payment_obj.gen_total_fee()
cpp_dic["total_cargo_price"] = total_fee_dic["cargo_price"]
cpp_dic["total_deduction_fee"] = total_fee_dic["deduction_fee"]
cpp_dic["total_cargo_handling_fee"] = total_fee_dic["cargo_handling_fee"]
cpp_dic["final_fee"] = (
total_fee_dic["cargo_price"] - total_fee_dic["deduction_fee"] - total_fee_dic["cargo_handling_fee"]
)
return cpp_dic
此差异已折叠。
此差异已折叠。
from .models import User, CargoPricePayment, Waybill, TransportOut, DepartmentPayment
def pros(request):
return {
"USER_TYPES": User.Types,
"CPP_STATUSES": CargoPricePayment.Statuses,
"WB_STATUSES": Waybill.Statuses,
"WB_FEE_TYPES": Waybill.FeeTypes,
"TO_STATUSES": TransportOut.Statuses,
"DP_STATUSES": DepartmentPayment.Statuses,
}
<div class="{{ div_class }}" style="{% if field.field.widget.attrs.hidden or field.is_hidden %}display: none;{% endif %}">
<label class="{% if field.field.required %}label-required{% endif %}" for="{{ field.id_for_label }}">
{{ label | default:"" }}
</label>
{{ field }}
</div>
<div class="{{ div_class }}" style="{% if field.field.widget.attrs.hidden or field.is_hidden %}display: none;{% endif %}">
<div class="input-group row" style="margin-left: 0;">
<label class="col-12 {% if field.field.required %}label-required{% endif %}" style="padding-left: 0;" for="{{ field.id_for_label }}">
{{ label | default:field.label | default:"" }}
</label>
{{ field }}
<div class="input-group-append" data-for="{{ field_append.html_name }}">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ field_append_initial_string }}
</button>
<div class="dropdown-menu">
{% for choice in field_append_choices %}
<a class="dropdown-item" data-select_value="{{ choice.0 }}" href="javascript:">{{ choice.1 }}</a>
{% endfor %}
</div>
</div>
</div>
</div>
<select name="{{ field_append.html_name }}" hidden>
{% for choice in field_append_choices %}
<option value="{{ choice.0 }}" {% if field_append_initial_value == choice.0 %}selected{% endif %}></option>
{% endfor %}
</select>
<script>
$(function() {
$('.input-group-append[data-for="{{ field_append.html_name }}"] .dropdown-item').click(function() {
$('select[name="{{ field_append.html_name }}"]').val($(this).attr("data-select_value"));
$(this).parents(".input-group-append").find(".dropdown-toggle").html($(this).html());
});
});
</script>
{% load wuliu_extras %}
<div class="card bg-light mt-2"><div class="card-body" id="{{ div_id }}">
{% _show_permission_tree list %}
</div></div>
<script>
$(function() {
{# 移除首个缩进 #}
$("#{{ div_id }} ol:first").removeClass("tree-pl");
});
$(document).ready(function() {
$("#{{ div_id }} .fa-minus-square,.fa-plus-square").click(function() {
$($(this).parents("ol")[0]).children("ol").toggle();
if ($(this).hasClass("fa-plus-square"))
$(this).removeClass("fa-plus-square").addClass("fa-minus-square");
else if ($(this).hasClass("fa-minus-square"))
$(this).removeClass("fa-minus-square").addClass("fa-plus-square");
});
$("#{{ div_id }} input:checkbox").change(function() {
let this_is_checked = $(this).is(":checked");
for (let ol of $($(this).parents("ol")[0]).children("ol")) {
for (let checkbox of $(ol).find("input:checkbox")) {
if (this_is_checked) {
if (! $(checkbox).is(":checked")) checkbox.click();
} else {
if ($(checkbox).is(":checked")) checkbox.click();
}
}
}
});
});
</script>
<script>
let {{ table_id }}_latest_export_date;
$(document).ready(function() {
$('{{ button_css_selector }}').click(function() {
if ({{ table_id }}_latest_export_date !== undefined) {
let time_out = parseInt((new Date() - {{ table_id }}_latest_export_date) / 1000)
if (time_out < {{ min_time_interval }}) {
mdtoast_error("" + ({{ min_time_interval }} - time_out) + "秒后再试!")
return;
}
}
let table_title = {% if table_title %}{{ table_title | safe }}{% else %}$(".content-header h1").text(){% endif %};
let table_rows = []
for (let row of {{ table_id }}_table.rows().data().toArray()) {
let row_data = [];
for (let index in {{ table_id }}_table.columns().header().toArray()) {
row_data[index] = typeof(row[index]) == "string" ? row[index] : row[index].display;
if (typeof(row[index]) == "string") {
row_data[index] = row[index];
} else {
row_data[index] = row[index].display;
}
if (row_data[index] && row_data[index][0] === "<")
row_data[index] = $(row_data[index]).text();
row_data[index] = row_data[index].trim()
}
table_rows.push(row_data.slice({{ skip_td_num }}))
}
let table_header = [];
for (let header_info of {{ table_id }}_table.columns().header().toArray()) {
table_header.push(header_info.innerText)
}
confirm_dialog(
"导出", "确定要导出报表吗?",
{
okClick: function() {
this.hide();
$.StandardPost(
"{% url 'utils:export_excel' %}",
{
"table_title": table_title,
"table_header": JSON.stringify(table_header.slice({{ skip_td_num }})),
"table_rows": JSON.stringify(table_rows),
},
)
{{ table_id }}_latest_export_date = new Date();
}
}
);
});
});
</script>
<script>
const {{ table_id }}_table = $("#{{ table_id }}").DataTable({
"autoWidth": false,
"buttons": [
{
extend: 'colvis',
columns: ':gt({% if have_check_box %}2{% else %}1{% endif %})',
text: "<i class='fas fa-columns pr-1'></i>",
},
],
"dom":
"<'row'<'col-sm-12 col-md-6'<'d-flex'<'p-1 pt-2'l><'p-1'B>>><'col-sm-12 col-md-6 child-flex child-flex-xr child-flex-yc'f>>" +
"<'row'<'col-12'tr>>" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7 pt-2'p>>",
"scrollX": true,
"scrollY": "400px",
"lengthMenu": [10, 25, 50, 100],
"columnDefs": [
{
"targets": {% if have_check_box %}[0, 1]{% else %}[0]{% endif %},
"orderable": false,
},
],
"order": [[ 2, "asc" ]],
"fixedColumns": {
{# 窗口宽度与高度之比小于4:3时只能固定1列(无checkbox)或两列(有checkbox) #}
left: (
window.innerWidth / window.innerHeight < 1.33 ?
{% if have_check_box %}2{% else %}1{% endif %} :
{% if custom_fixed_columns_left is None %}{% if have_check_box %}3{% else %}2{% endif %}{% else %}{{ custom_fixed_columns_left }}{% endif %}
)
},
"language": {
"emptyTable": "暂无数据",
"infoEmpty": "暂无数据",
"lengthMenu": "显示 _MENU_ 条/页",
"info": "共 _MAX_ 条 | 第 _PAGE_ / _PAGES_ 页",
"search": "搜索:",
"paginate": {
"first": "&laquo;",
"last": "&raquo;",
"next": "&gt;",
"previous": "&lt;",
}
}
})
{% if have_check_box %}
$('#{{ table_id }}_checkbox_all').change(function() {
if ($(this).is(":checked")) {
{{ table_id }}_table.rows().nodes().each(function(row) {
if (! $(row).find("input:checkbox").is(":checked")) {
$(row).find("input:checkbox").click();
}
});
} else {
{{ table_id }}_table.rows().nodes().each(function(row) {
if ($(row).find("input:checkbox").is(":checked")) {
$(row).find("input:checkbox").click();
}
});
}
});
{% endif %}
{{ table_id }}_table.on("draw.dt search.dt", function() {
{{ table_id }}_table.column(0).nodes().each(function(cell, i) {
cell.innerHTML = '<span style="font-weight: bold">' + (i + 1) + "</span>";
});
}).draw();
</script>
<div class="alert alert-{{ message.level_tag }} {{ message.extra_tags }} alert-dismissible fade show text-md">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<i class="icon {{ icon }}"></i>
{{ message.message }}
</div>
{% load wuliu_extras %}
{% load bool from utils_extras %}
{% for child in list %}
<ol class="nav flex-column tree-pl">
<li>
<div class="custom-control custom-checkbox {% if not child.children %}permission-padding-fix{% endif %}">
{% if child.children %}
<i class="far fa-minus-square tree-icon-pl"></i>
{% else %}
<span class="tree-icon-pl"></span>
{% endif %}
<input type="checkbox" class="custom-control-input" data-is_group="{{ child.children | bool }}" data-select_value="{{ child.id }}" id="{{ child.name }}_checkbox" {% if child.enabled %}checked{% endif %}>
<label class="custom-control-label custom-checkbox-label-fix" for="{{ child.name }}_checkbox">{{ child.print_name }}</label>
</div>
</li>
{% if child.children %}
{% _show_permission_tree child.children %}
{% endif %}
</ol>
{% endfor %}
{% load wuliu_extras %}
{% for item in items %}
<li class="nav-item has-treeview {% if item.opened %}menu-open{% endif %}">
<a href="{{ item.url | default:'#' }}" class="nav-link {% if item.opened %}active{% endif %}">
<i class="{{ item.icon | default:'fas fa-circle' }} nav-icon"></i>
<p>
{{ item.name }}
<i class="right fas fa-angle-left"></i>
</p>
</a>
<ul class="nav nav-treeview">
{% for item_child in item.children %}
{# 如果item_child不需要任何权限, 或item_child需要权限且用户拥有该权限 #}
{% if not item_child.need_perm or item_child.need_perm and item_child.need_perm|is_logged_user_has_perm:request %}
{# 如果item_child不需要管理员权限, 或item_child需要管理员权限且用户是管理员 #}
{% if not item_child.admin_only or request|is_logged_user_is_admin %}
<li class="nav-item">
<a href="{{ item_child.url | default:'#' }}" class="nav-link {% if item_child.opened %}active{% endif %}">
<i class="{{ item_child.icon | default:'far fa-circle' }} nav-icon"></i>
<p>{{ item_child.name }}</p>
</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
</ul>
</li>
{% endfor %}
\ No newline at end of file
<div class="card bg-light shadow" data-waybill_id="{{ waybill.id }}">
<div class="card-header">
<div class="row">
<div class="col-12 col-md">
<i class="fas fa-angle-right mr-2"></i>运单号:<span>{{ waybill.get_full_id }}</span>
</div>
<div class="col-12 col-md">
提付运费:<span data-fee>{% if waybill.fee_type == WB_FEE_TYPES.SignFor %}{{ waybill.fee }}{% else %}0{% endif %}</span>
</div>
<div class="col-12 col-md">
代收款:<span data-cargo_price>{{ waybill.cargo_price }}</span>
</div>
<div class="col-12 col-md">
应付金额:<span data-final_fee>
{% if waybill.fee_type == WB_FEE_TYPES.SignFor %}
{{ waybill.fee | add:waybill.cargo_price }}
{% else %}
{{ waybill.cargo_price }}
{% endif %}
</span>
</div>
<div class="d-flex justify-content-end">
<a class="waybill-info-remove" href="javascript:"><span>移除</span></a>
</div>
</div>
</div>
<div class="card-body" style="display: none;">
<div class="row">
<div class="col-md-3">
发货人:<code>{{ waybill.src_customer_name }}</code>
</div>
<div class="col-md-3">
发货人电话:<code>{{ waybill.src_customer_phone }}</code>
</div>
<div class="col-md-3">
收货人:<code>{{ waybill.dst_customer_name }}</code>
</div>
<div class="col-md-3">
收货人电话:<code>{{ waybill.dst_customer_phone }}</code>
</div>
<div class="col-md-3">
发货部门:<code>{{ waybill.src_department.name }}</code>
</div>
<div class="col-md-3">
货物名称:<code>{{ waybill.cargo_name }}</code>
</div>
<div class="col-md-3">
件数:<code>{{ waybill.cargo_num }}</code>
</div>
<div class="col-md-3">
结算方式:<code>{{ waybill.get_fee_type_display }}</code>
</div>
<div class="col-md-6">
客户备注:<code>{{ waybill.customer_remark | default:"无" }}</code>
</div>
<div class="col-md-6">
公司备注:<code>{{ waybill.company_remark | default:"无" }}</code>
</div>
</div>
</div>
</div>
\ No newline at end of file
{% load wuliu_extras %}
{% load subtract from utils_extras %}
<div class="card card-primary card-outline shadow"><div class="card-body">
<table id="{{ table_id }}" class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>
<div class="custom-control custom-checkbox ml-2">
<input type="checkbox" class="custom-control-input" id="{{ table_id }}_checkbox_all">
<label class="custom-control-label" for="{{ table_id }}_checkbox_all"></label>
</div>
</th>
<th>账单编号</th>
<th>状态</th>
<th>创建时间</th>
<th>结算时间</th>
<th>创建人</th>
<th>收款人姓名</th>
<th>收款人电话号码</th>
<th>收款人银行名称</th>
<th>收款人银行卡号</th>
<th>收款人身份证号</th>
<th>应付货款</th>
<th>扣付运费</th>
<th>手续费</th>
<th>实付金额</th>
<th>备注</th>
</tr>
</thead>
<tbody>
{% for cargo_price_payment in cargo_price_payment_list %}
<tr>
<td></td>
<td data-cpp_id="{{ cargo_price_payment.id }}">
<div class="custom-control custom-checkbox ml-2">
<input type="checkbox" class="custom-control-input" id="{{ table_id }}_to_{{ cargo_price_payment.id }}">
<label class="custom-control-label" for="{{ table_id }}_to_{{ cargo_price_payment.id }}"></label>
</div>
</td>
<td data-order="{{ cargo_price_payment.id }}"><a href="{% url 'wuliu:detail_cargo_price_payment' cargo_price_payment.id %}">{{ cargo_price_payment.get_full_id }}</a></td>
<td data-key="cpp_status" data-status_id="{{ cargo_price_payment.status }}" data-order="{{ cargo_price_payment.status }}">{{ cargo_price_payment.get_status_display }}</td>
<td data-key="cpp_create_time" data-order="{{ cargo_price_payment.create_time.timestamp }}">{{ cargo_price_payment.create_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="cpp_settle_accounts_time" data-order="{{ cargo_price_payment.settle_accounts_time.timestamp }}">{{ cargo_price_payment.settle_accounts_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="cpp_create_user" data-user_id="{{ cargo_price_payment.create_user.id }}">{{ cargo_price_payment.create_user.name }}</td>
<td data-key="cpp_payee_name">{{ cargo_price_payment.payee_name }}</td>
<td data-key="cpp_payee_phone">{{ cargo_price_payment.payee_phone }}</td>
<td data-key="cpp_payee_bank_name">{{ cargo_price_payment.payee_bank_name }}</td>
<td data-key="cpp_payee_bank_number">{{ cargo_price_payment.payee_bank_number }}</td>
<td data-key="cpp_payee_credential_num">{{ cargo_price_payment.payee_credential_num }}</td>
{% with total_fee=cargo_price_payment.gen_total_fee %}
<td data-key="cpp_total_cargo_price">{{ total_fee.cargo_price }}</td>
<td data-key="cpp_total_deduction_fee">{{ total_fee.deduction_fee }}</td>
<td data-key="cpp_total_deduction_fee">{{ total_fee.cargo_handling_fee }}</td>
<td data-key="cpp_final_fee">{{ total_fee.cargo_price | subtract:total_fee.deduction_fee | subtract:total_fee.cargo_handling_fee }}</td>
{% endwith %}
<td data-key="cpp_remark">{{ cargo_price_payment.remark }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div></div>
{% js_init_datatable table_id True 4 %}
{% load wuliu_extras %}
{% load bool from utils_extras %}
<div class="card card-primary card-outline shadow"><div class="card-body">
<table id="{{ table_id }}" class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>会员姓名</th>
<th>会员电话</th>
<th>变动方式</th>
<th>变动积分</th>
<th>变动时间</th>
<th>操作人</th>
<th>变更原因</th>
<th>关联运单</th>
</tr>
</thead>
<tbody>
{% for customer_score_log in customer_score_logs %}
<tr>
<td></td>
<td>
<a href="javascript: show_customer_score({{ customer_score_log.customer.id }})">{{ customer_score_log.customer.name }}</a>
</td>
<td>{{ customer_score_log.customer.phone }}</td>
<td data-inc_or_dec="{{ customer_score_log.inc_or_dec | bool }}">{% if customer_score_log.inc_or_dec %}增加{% else %}扣减{% endif %}</td>
<td data-order="{{ customer_score_log.score }}">{{ customer_score_log.score }}</td>
<td data-order="{{ customer_score_log.create_time.timestamp }}">{{ customer_score_log.create_time | date:"Y-m-d H:i:s" }}</td>
<td>{% if customer_score_log.user %}{{ customer_score_log.user.name }}{% else %}<span style="font-style: italic">系统自动生成</span>{% endif %}</td>
<td>{{ customer_score_log.remark }}</td>
<td>
{% if customer_score_log.waybill %}
<a href="{% url 'wuliu:detail_waybill' customer_score_log.waybill.id %}">{{ customer_score_log.waybill.get_full_id }}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div></div>
{% js_init_datatable table_id False %}
<script>
function show_customer_score(customer_id) {
$.ajax({
url: "{% url 'wuliu:api_get_customer_info' %}?customer_id=" + customer_id,
type: "GET",
async: false,
dataType: "json",
success: function(data, status) {
new duDialog(
"客户:" + data.data.customer_info.name,
"当前积分:" + data.data.customer_info.score,
{okText: "确认"}
);
},
});
}
</script>
{% load wuliu_extras %}
{% load sum from utils_extras %}
<div class="card card-primary card-outline shadow"><div class="card-body">
<table id="{{ table_id }}" class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>
<div class="custom-control custom-checkbox ml-2">
<input type="checkbox" class="custom-control-input" id="{{ table_id }}_checkbox_all">
<label class="custom-control-label" for="{{ table_id }}_checkbox_all"></label>
</div>
</th>
<th>账单编号</th>
<th>状态</th>
<th>应回款日期</th>
<th>创建时间</th>
<th>结算时间</th>
<th>回款部门</th>
<th>收款部门</th>
<th>应回款金额总计</th>
<th>现付运费合计</th>
<th>提付运费合计</th>
<th>代收货款合计</th>
<th hidden>回款部门备注</th>
<th hidden>收款部门备注</th>
</tr>
</thead>
<tbody>
{% for department_payment in department_payment_list %}
<tr>
<td></td>
<td data-dp_id="{{ department_payment.id }}">
<div class="custom-control custom-checkbox ml-2">
<input type="checkbox" class="custom-control-input" id="{{ table_id }}_to_{{ department_payment.id }}">
<label class="custom-control-label" for="{{ table_id }}_to_{{ department_payment.id }}"></label>
</div>
</td>
<td data-order="{{ department_payment.id }}"><a href="{% url 'wuliu:detail_department_payment' department_payment.id %}">{{ department_payment.get_full_id }}</a></td>
<td data-key="dp_status" data-status_id="{{ department_payment.status }}" data-order="{{ department_payment.status }}">{{ department_payment.get_status_display }}</td>
<td data-key="dp_payment_date">{{ department_payment.payment_date | date:"Y-m-d" }}</td>
<td data-key="dp_create_time" data-order="{{ department_payment.create_time.timestamp }}">{{ department_payment.create_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="dp_settle_accounts_time" data-order="{{ department_payment.settle_accounts_time.timestamp }}">{{ department_payment.settle_accounts_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="dp_src_department" data-src_dept_id="{{ department_payment.src_department.id }}">{{ department_payment.src_department.name }}</td>
<td data-key="dp_dst_department" data-dst_dept_id="{{ department_payment.dst_department.id }}">{{ department_payment.dst_department.name }}</td>
{% with total_fee_dic=department_payment.gen_total_fee %}
<td data-key="dp_final_total_fee">{{ total_fee_dic.values | sum }}</td>
<td data-key="dp_total_fee_now">{{ total_fee_dic.fee_now }}</td>
<td data-key="dp_total_fee_sign_for">{{ total_fee_dic.fee_sign_for }}</td>
<td data-key="dp_total_cargo_price">{{ total_fee_dic.cargo_price }}</td>
{% endwith %}
<td hidden data-key="dp_src_remark">{{ department_payment.src_remark }}</td>
<td hidden data-key="dp_dst_remark">{{ department_payment.dst_remark }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div></div>
{% js_init_datatable table_id True 4 %}
{% load wuliu_extras %}
<div class="card card-primary card-outline shadow"><div class="card-body">
<table id="{{ table_id }}" class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>运单号码</th>
<th>开票日期</th>
<th>到货日期</th>
<th>滞留天数</th>
<th>发货部门</th>
<th>到达部门</th>
<th>发货人</th>
<th>发货人电话</th>
<th>收货人</th>
<th>收货人电话</th>
<th>货物名称</th>
<th>件数</th>
<th>体积</th>
<th>重量</th>
<th>代收货款</th>
<th>运费</th>
<th>结算方式</th>
</tr>
</thead>
<tbody>
{% for waybill in waybills_info_list %}
<tr>
<td></td>
<td data-order="{{ waybill.id }}"><a href="{% url 'wuliu:detail_waybill' waybill.id %}">{{ waybill.get_full_id }}</a></td>
<td data-key="wb_create_time" data-order="{{ waybill.create_time.timestamp }}">{{ waybill.create_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_arrival_time" data-order="{{ waybill.arrival_time.timestamp }}">{{ waybill.arrival_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_stay_days" data-order="{{ waybill.stay_days }}">{{ waybill.stay_days }}</td>
<td data-key="wb_src_department" data-src_dept_id="{{ waybill.src_department.id }}">{{ waybill.src_department.name }}</td>
<td data-key="wb_dst_department" data-dst_dept_id="{{ waybill.dst_department.id }}">{{ waybill.dst_department.name }}</td>
<td data-key="wb_src_customer_name">{{ waybill.src_customer_name }}</td>
<td data-key="wb_dst_customer_name">{{ waybill.src_customer_phone }}</td>
<td data-key="wb_dst_customer_name">{{ waybill.dst_customer_name }}</td>
<td data-key="wb_dst_customer_phone">{{ waybill.dst_customer_phone }}</td>
<td data-key="wb_cargo_name">{{ waybill.cargo_name }}</td>
<td data-key="wb_cargo_num">{{ waybill.cargo_num }}</td>
<td data-key="wb_cargo_volume">{{ waybill.cargo_volume | floatformat:2 }}</td>
<td data-key="wb_cargo_weight">{{ waybill.cargo_weight | floatformat }}</td>
<td data-key="wb_cargo_price">{{ waybill.cargo_price }}</td>
<td data-key="wb_fee">{{ waybill.fee }}</td>
<td data-key="wb_fee_type" data-fee_type_id="{{ waybill.fee_type }}">{{ waybill.get_fee_type_display }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div></div>
{% js_init_datatable table_id False %}
{% load wuliu_extras %}
<div class="card card-primary card-outline shadow"><div class="card-body">
<table id="{{ table_id }}" class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>运单号码</th>
<th>运单状态</th>
<th>开票日期</th>
<th>开票部门</th>
<th>到达部门</th>
<th>运费</th>
<th>开票部门发车时间</th>
<th>货场入库时间</th>
<th>货场发车时间</th>
<th>到货时间</th>
</tr>
</thead>
<tbody>
{% for waybill in waybills_info_list %}
<tr>
<td></td>
<td data-order="{{ waybill.id }}"><a href="{% url 'wuliu:detail_waybill' waybill.id %}">{{ waybill.get_full_id }}</a></td>
<td data-key="wb_status" data-order="{{ waybill.status }}" data-status_id="{{ waybill.status }}">{{ waybill.get_status_display }}</td>
<td data-key="wb_create_time" data-order="{{ waybill.create_time.timestamp }}">{{ waybill.create_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_src_department" data-src_dept_id="{{ waybill.src_department.id }}">{{ waybill.src_department.name }}</td>
<td data-key="wb_dst_department" data-dst_dept_id="{{ waybill.dst_department.id }}">{{ waybill.dst_department.name }}</td>
<td data-key="wb_fee">{{ waybill.fee }}</td>
<td data-key="wb_departed_time" data-order="{{ waybill.departed_time.timestamp }}">{{ waybill.departed_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_goods_yard_arrived_time" data-order="{{ waybill.goods_yard_arrived_time.timestamp }}">{{ waybill.goods_yard_arrived_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_goods_yard_departed_time" data-order="{{ waybill.goods_yard_departed_time.timestamp }}">{{ waybill.goods_yard_departed_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_arrival_time" data-order="{{ waybill.arrival_time.timestamp }}">{{ waybill.arrival_time | date:"Y-m-d H:i:s" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div></div>
{% js_init_datatable table_id False 3 %}
{% load wuliu_extras %}
<div class="card card-primary card-outline shadow"><div class="card-body">
<table id="{{ table_id }}" class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>
<div class="custom-control custom-checkbox ml-2">
<input type="checkbox" class="custom-control-input" id="{{ table_id }}_checkbox_all">
<label class="custom-control-label" for="{{ table_id }}_checkbox_all"></label>
</div>
</th>
<th>车次编号</th>
<th>车次状态</th>
<th>创建时间</th>
<th>发车时间</th>
<th>到达时间</th>
<th>发车部门</th>
<th>到达部门</th>
<th>车牌号</th>
<th>驾驶员</th>
<th>驾驶员电话</th>
<th>货物总单数</th>
<th>货物总件数</th>
<th>货物总体积 (m³)</th>
<th>货物总重量 (Kg)</th>
</tr>
</thead>
<tbody>
{% for transport_out in transport_out_list %}
<tr>
<td></td>
<td data-to_id="{{ transport_out.id }}">
<div class="custom-control custom-checkbox ml-2">
<input type="checkbox" class="custom-control-input" id="{{ table_id }}_to_{{ transport_out.id }}">
<label class="custom-control-label" for="{{ table_id }}_to_{{ transport_out.id }}"></label>
</div>
</td>
<td data-order="{{ transport_out.id }}"><a href="{% url 'wuliu:detail_transport_out' %}?transport_out_id={{ transport_out.id }}">{{ transport_out.get_full_id }}</a></td>
<td data-key="to_status" data-order="{{ transport_out.status }}" data-status_id="{{ transport_out.status }}">{{ transport_out.get_status_display }}</td>
<td data-key="to_create_time" data-order="{{ transport_out.create_time.timestamp }}">{{ transport_out.create_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="to_start_time" data-order="{{ transport_out.start_time.timestamp }}">{{ transport_out.start_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="to_end_time" data-order="{{ transport_out.end_time.timestamp }}">{{ transport_out.end_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="to_src_department" data-src_dept_id="{{ transport_out.src_department.id }}">{{ transport_out.src_department.name }}</td>
<td data-key="to_dst_department" data-dst_dept_id="{{ transport_out.dst_department.id }}">{{ transport_out.dst_department.name }}</td>
<td data-key="to_truck" data-truck_id="{{ transport_out.truck.id }}">{{ transport_out.truck.number_plate }}</td>
<td data-key="to_driver_name">{{ transport_out.driver_name }}</td>
<td data-key="to_driver_phone">{{ transport_out.driver_phone }}</td>
<td data-key="to_total_num">{{ transport_out.total_num }}</td>
<td data-key="to_total_cargo_num">{{ transport_out.total_cargo_num }}</td>
<td data-key="to_total_cargo_volume">{{ transport_out.total_cargo_volume | floatformat:2 }}</td>
<td data-key="to_total_cargo_weight">{{ transport_out.total_cargo_weight | floatformat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div></div>
{% js_init_datatable table_id True 4 %}
{% load wuliu_extras %}
<div class="card card-primary card-outline shadow"><div class="card-body">
<table id="{{ table_id }}" class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
{% if have_check_box %}
<th>
<div class="custom-control custom-checkbox ml-2">
<input type="checkbox" class="custom-control-input" id="{{ table_id }}_checkbox_all">
<label class="custom-control-label" for="{{ table_id }}_checkbox_all"></label>
</div>
</th>
{% endif %}
<th>运单号码</th>
<th>运单状态</th>
<th>开票日期</th>
<th>到货日期</th>
<th>提货日期</th>
<th>发货部门</th>
<th>到达部门</th>
<th>发货人</th>
<th>发货人电话</th>
<th>收货人</th>
<th>收货人电话</th>
<th>货物名称</th>
<th>件数</th>
<th>体积</th>
<th>重量</th>
<th>代收货款</th>
<th>代收货款状态</th>
<th>运费</th>
<th>结算方式</th>
</tr>
</thead>
<tbody>
{% for waybill in waybills_info_list %}
{% show_waybill_table_row waybill table_id have_check_box %}
{% endfor %}
</tbody>
</table>
</div></div>
{% if have_check_box %}
{% js_init_datatable table_id True 4 %}
{% else %}
{% js_init_datatable table_id False 3 %}
{% endif %}
{% if high_light_fee %}
<script>
{{ table_id }}_table.rows().nodes().each(function(row) {
let td_src_dept = $(row).find("[data-src_dept_id]");
let td_dst_dept = $(row).find("[data-dst_dept_id]");
if (td_src_dept.attr("data-src_dept_id") == "{{ high_light_dept_id }}") {
td_src_dept.addClass("table-info");
$(row).find("td[data-key='wb_fee']").addClass("table-info");
} else if (td_dst_dept.attr("data-dst_dept_id") == "{{ high_light_dept_id }}") {
td_dst_dept.addClass("table-info");
$(row).find("td[data-key='wb_cargo_price']").addClass("table-info");
if ($(row).find("[data-fee_type_id]").attr("data-fee_type_id") == "{{ WB_FEE_TYPES.SignFor }}") {
$(row).find("td[data-key='wb_fee']").addClass("table-info");
}
}
});
</script>
{% endif %}
<tr>
<td></td>
{% if have_check_box %}
<td data-wb_id="{{ waybill.id }}">
<div class="custom-control custom-checkbox ml-2">
<input type="checkbox" class="custom-control-input" id="{{ table_id }}_waybill_{{ waybill.id }}">
<label class="custom-control-label" for="{{ table_id }}_waybill_{{ waybill.id }}"></label>
</div>
</td>
{% endif %}
<td data-order="{{ waybill.id }}"><a href="{% url 'wuliu:detail_waybill' waybill.id %}">{{ waybill.get_full_id }}</a></td>
<td data-key="wb_status" data-order="{{ waybill.status }}" data-status_id="{{ waybill.status }}">{{ waybill.get_status_display }}</td>
<td data-key="wb_create_time" data-order="{{ waybill.create_time.timestamp }}">{{ waybill.create_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_arrival_time" data-order="{{ waybill.arrival_time.timestamp }}">{{ waybill.arrival_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_sign_for_time" data-order="{{ waybill.sign_for_time.timestamp }}">{{ waybill.sign_for_time | date:"Y-m-d H:i:s" }}</td>
<td data-key="wb_src_department" data-src_dept_id="{{ waybill.src_department.id }}">{{ waybill.src_department.name }}</td>
<td data-key="wb_dst_department" data-dst_dept_id="{{ waybill.dst_department.id }}">{{ waybill.dst_department.name }}</td>
<td data-key="wb_src_customer_name">{{ waybill.src_customer_name }}</td>
<td data-key="wb_dst_customer_name">{{ waybill.src_customer_phone }}</td>
<td data-key="wb_dst_customer_name">{{ waybill.dst_customer_name }}</td>
<td data-key="wb_dst_customer_phone">{{ waybill.dst_customer_phone }}</td>
<td data-key="wb_cargo_name">{{ waybill.cargo_name }}</td>
<td data-key="wb_cargo_num">{{ waybill.cargo_num }}</td>
<td data-key="wb_cargo_volume">{{ waybill.cargo_volume | floatformat:2 }}</td>
<td data-key="wb_cargo_weight">{{ waybill.cargo_weight | floatformat }}</td>
<td data-key="wb_cargo_price">{{ waybill.cargo_price }}</td>
<td data-key="wb_cargo_price_status" data-order="{{ waybill.cargo_price_status }}">{{ waybill.get_cargo_price_status_display }}</td>
<td data-key="wb_fee">{{ waybill.fee }}</td>
<td data-key="wb_fee_type" data-fee_type_id="{{ waybill.fee_type }}">{{ waybill.get_fee_type_display }}</td>
</tr>
此差异已折叠。
// <script>
$(document).ready(function() {
$('#form-add_cargo_price_payment_waybill').submit(function(e) {
e.preventDefault();
let add_waybill_id = $('#form-add_cargo_price_payment_waybill [name="add_waybill_id"]').val().trim();
if (! add_waybill_id) return;
let current_cpp_id = $('#form-payee_info [name="id"]').val();
if (! current_cpp_id) current_cpp_id = "";
$.post(
"{% url 'wuliu:api_add_waybill_when_edit_cargo_price_payment' %}",
{
"add_waybill_id": add_waybill_id,
"current_cpp_id": current_cpp_id,
"table_id": "cpp_waybill",
},
function(data, status) {
if (data.code === 200) {
let added_waybill_ids = [];
cpp_waybill_table.rows().nodes().each(function(row) {
added_waybill_ids.push(parseInt($(row).find("[data-wb_id]").attr("data-wb_id")));
});
if (added_waybill_ids.indexOf(data.data.waybill_id) !== -1) {
$('#form-payee_info [name="add_waybill_id"]').val("");
return;
}
cpp_waybill_table.row.add($(data.data.html)).draw();
} else {
mdtoast_error("添加失败!" + data.data.message);
}
$('#form-add_cargo_price_payment_waybill [name="add_waybill_id"]').val("");
}
)
});
$("#button_wb_remove").click(function() {
cpp_waybill_table.rows().nodes().each(function(row) {
if ($(row).find("input:checkbox").is(":checked")) {
cpp_waybill_table.row(row).remove();
}
});
cpp_waybill_table.draw();
});
$('#form-payee_info [name="customer"]').change(function() {
let customer_id = $(this).val();
if (customer_id) {
$.getJSON("{% url 'wuliu:api_get_customer_info' %}?customer_id=" + customer_id, function(result) {
let customer_info = result.data.customer_info;
$('#form-payee_info [name="payee_name"]').val(customer_info.name);
$('#form-payee_info [name="payee_phone"]').val(customer_info.phone);
$('#form-payee_info [name="payee_bank_name"]').val(customer_info.bank_name);
$('#form-payee_info [name="payee_bank_number"]').val(customer_info.bank_number);
$('#form-payee_info [name="payee_credential_num"]').val(customer_info.credential_num);
});
} else {
for (let field_name of ["payee_name", "payee_phone", "payee_bank_name", "payee_bank_number", "payee_credential_num"]) {
$('#form-payee_info [name="'+field_name+'"]').val("");
}
}
});
$("#button_cargo_price_payment_submit").click(function() {
for (let field_name of ["payee_name", "payee_phone", "payee_bank_name", "payee_bank_number", "payee_credential_num"]) {
if (! $('#form-payee_info input[name="'+field_name+'"]').val().trim()) {
mdtoast_error("请完善收款人信息!");
return;
}
}
let waybill_ids = find_datatable_rows_all(cpp_waybill_table).map(function(jq_obj) {
return jq_obj.find("[data-wb_id]").attr("data-wb_id");
});
if (waybill_ids.length === 0) {
mdtoast_error("你没有选择任何运单。");
return;
}
$('#form-payee_info input[name="waybill_ids"]').val(waybill_ids.join(","));
$('#form-payee_info').submit();
});
});
// </script>
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
{% extends "wuliu/finance/cargo_price_payment/_layout_edit_cargo_price_payment.html" %}
{% block title %}新建代收款转账单{% endblock %}
{% block header_title %}新建代收款转账单{% endblock %}
{% block form_action %}{% url 'wuliu:add_cargo_price_payment' %}{% endblock %}
{% block action_bar %}
<button class="btn btn-outline-primary btn-sm" id="button_wb_remove">
<i class="ri-c ri-delete-bin-line"><span>删除</span></i>
</button>
{% endblock %}
{% block action_buttons %}
<button class="btn btn-primary" id="button_cargo_price_payment_submit">提交</button>
{% endblock %}
{% extends "wuliu/finance/cargo_price_payment/add_cargo_price_payment.html" %}
{% block title %}修改代收款转账单{% endblock %}
{% block header_title %}修改代收款转账单{% endblock %}
{% block form_action %}{% url 'wuliu:edit_cargo_price_payment' %}{% endblock %}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册