chore: re-initialize git repository for deployment
@@ -0,0 +1,93 @@
|
||||
# --- Python ---
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
*.egg
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
dist/
|
||||
build/
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.python-version
|
||||
|
||||
# --- Django ---
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Collected static (if STATIC_ROOT differs from source)
|
||||
staticfiles/
|
||||
collected_static/
|
||||
|
||||
# Compiled translations (generate via compilemessages)
|
||||
*.mo
|
||||
|
||||
# --- Environment & secrets ---
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.pem
|
||||
*.key
|
||||
|
||||
# --- IDE / Editor ---
|
||||
.idea/
|
||||
.vscode/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# --- OS ---
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
desktop.ini
|
||||
|
||||
# --- Cursor / local workflow ---
|
||||
.cursor/
|
||||
.work/
|
||||
|
||||
# --- Test & coverage ---
|
||||
.coverage
|
||||
.coverage.*
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# --- Docker (local overrides & volumes) ---
|
||||
docker-compose.override.yml
|
||||
docker-compose.local.yml
|
||||
*.volume/
|
||||
|
||||
# --- User uploads (MEDIA_ROOT) ---
|
||||
# Keep template docs under upload/doc/
|
||||
upload/data/*
|
||||
!upload/data/.gitkeep
|
||||
upload/inventory/*
|
||||
!upload/inventory/.gitkeep
|
||||
upload/project/*
|
||||
!upload/project/.gitkeep
|
||||
upload/offer sheet/*
|
||||
!upload/offer sheet/.gitkeep
|
||||
|
||||
# --- Misc ---
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
.work/
|
||||
@@ -0,0 +1,22 @@
|
||||
FROM python:2.7-slim
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python dependencies
|
||||
# Using PyMySQL which is pure python, so we might not need gcc/mysql-client-dev
|
||||
COPY Install/requirements.txt /app/Install/requirements.txt
|
||||
RUN pip install --no-cache-dir -r Install/requirements.txt
|
||||
|
||||
# Copy project
|
||||
COPY . /app/
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Run server
|
||||
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||
@@ -0,0 +1,45 @@
|
||||
-- 销售订单示例数据(依赖 basedata_partner / basedata_material 等基础数据)
|
||||
-- 导入: mysql -uroot -proot mis < Install/SQL/sale_order_sample.sql
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO `sale_saleorder`
|
||||
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
|
||||
SELECT 1,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00001','2025-05-10','2025-05-25','办公设备采购订单','含笔记本电脑及服务器,需上门安装调试。','张经理','13800138001',NULL,'上海市浦东新区张江高科技园区科苑路88号','10',24299.00,0.00,'9',1,17,11
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00001');
|
||||
|
||||
INSERT INTO `sale_saleorder`
|
||||
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
|
||||
SELECT 2,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00002','2025-05-15','2025-06-01','配电设备批量采购','项目现场配电改造用材。','李工','13900139002',NULL,'江苏省南京市江宁区将军大道29号','10',6400.00,200.00,'1',1,17,11
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00002');
|
||||
|
||||
INSERT INTO `sale_saleorder`
|
||||
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
|
||||
SELECT 3,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00003','2025-05-18','2025-05-22','水果批发季节订单','当季鲜果直采,分批配送。','王采购','13700137003',NULL,'浙江省杭州市余杭区良渚街道物流园A区','10',13962.00,0.00,'0',1,17,11
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00003');
|
||||
|
||||
INSERT INTO `sale_saleorder`
|
||||
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
|
||||
SELECT 4,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00004','2025-05-20','2025-05-28','劳保用品集中采购','车间一线员工劳保手套补充采购。','赵主任','13600136004',NULL,'安徽省合肥市蜀山区望江西路666号','10',5500.00,0.00,'9',1,17,11
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00004');
|
||||
|
||||
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||
SELECT 1,2.0000,NULL,7150.0000,'0.00',NOW(),0,NULL,1,53,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=1);
|
||||
|
||||
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||
SELECT 2,1.0000,NULL,9999.0000,'0.00',NOW(),0,NULL,1,54,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=2);
|
||||
|
||||
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||
SELECT 3,20.0000,NULL,320.0000,'0.00',NOW(),0,NULL,2,52,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=3);
|
||||
|
||||
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||
SELECT 4,500.0000,NULL,10.9200,'0.00',NOW(),0,NULL,3,40,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=4);
|
||||
|
||||
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||
SELECT 5,300.0000,NULL,15.9900,'0.00',NOW(),0,NULL,3,41,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=5);
|
||||
|
||||
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||
SELECT 6,200.0000,NULL,18.5250,'0.00',NOW(),0,NULL,3,42,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=6);
|
||||
|
||||
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||
SELECT 7,1000.0000,NULL,5.5000,'0.00',NOW(),0,NULL,4,5,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=7);
|
||||
@@ -0,0 +1,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""导入销售订单示例数据。用法: python Install/load_sale_sample.py"""
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if BASE_DIR not in sys.path:
|
||||
sys.path.insert(0, BASE_DIR)
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mis.settings')
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from decimal import Decimal
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import transaction
|
||||
from basedata.models import Partner, Material, Organization
|
||||
from sale.models import SaleOrder, SaleItem
|
||||
|
||||
|
||||
def recalc_order_amount(order):
|
||||
total = Decimal('0')
|
||||
for item in SaleItem.objects.filter(master=order):
|
||||
price = item.sale_price or Decimal('0')
|
||||
total += price * item.cnt
|
||||
SaleOrder.objects.filter(pk=order.pk).update(amount=total)
|
||||
order.amount = total
|
||||
return total
|
||||
|
||||
|
||||
SAMPLE_ORDERS = [
|
||||
{
|
||||
'code': 'SO00001',
|
||||
'title': u'办公设备采购订单',
|
||||
'order_date': datetime.date(2025, 5, 10),
|
||||
'deliver_date': datetime.date(2025, 5, 25),
|
||||
'contact': u'张经理',
|
||||
'phone': '13800138001',
|
||||
'deliver_address': u'上海市浦东新区张江高科技园区科苑路88号',
|
||||
'description': u'含笔记本电脑及服务器,需上门安装调试。',
|
||||
'status': '9',
|
||||
'discount_amount': 0,
|
||||
'items': [
|
||||
{'material_code': 'IT5001', 'cnt': 2, 'sale_price': 7150.00},
|
||||
{'material_code': 'IT5002', 'cnt': 1, 'sale_price': 9999.00},
|
||||
],
|
||||
},
|
||||
{
|
||||
'code': 'SO00002',
|
||||
'title': u'配电设备批量采购',
|
||||
'order_date': datetime.date(2025, 5, 15),
|
||||
'deliver_date': datetime.date(2025, 6, 1),
|
||||
'contact': u'李工',
|
||||
'phone': '13900139002',
|
||||
'deliver_address': u'江苏省南京市江宁区将军大道29号',
|
||||
'description': u'项目现场配电改造用材。',
|
||||
'status': '1',
|
||||
'discount_amount': 200.00,
|
||||
'items': [
|
||||
{'material_code': 'IT9981', 'cnt': 20, 'sale_price': 320.00},
|
||||
],
|
||||
},
|
||||
{
|
||||
'code': 'SO00003',
|
||||
'title': u'水果批发季节订单',
|
||||
'order_date': datetime.date(2025, 5, 18),
|
||||
'deliver_date': datetime.date(2025, 5, 22),
|
||||
'contact': u'王采购',
|
||||
'phone': '13700137003',
|
||||
'deliver_address': u'浙江省杭州市余杭区良渚街道物流园A区',
|
||||
'description': u'当季鲜果直采,分批配送。',
|
||||
'status': '0',
|
||||
'discount_amount': 0,
|
||||
'items': [
|
||||
{'material_code': 'IT9001', 'cnt': 500, 'sale_price': 11.00},
|
||||
{'material_code': 'IT9002', 'cnt': 300, 'sale_price': 16.00},
|
||||
{'material_code': 'IT9003', 'cnt': 200, 'sale_price': 19.00},
|
||||
],
|
||||
},
|
||||
{
|
||||
'code': 'SO00004',
|
||||
'title': u'劳保用品集中采购',
|
||||
'order_date': datetime.date(2025, 5, 20),
|
||||
'deliver_date': datetime.date(2025, 5, 28),
|
||||
'contact': u'赵主任',
|
||||
'phone': '13600136004',
|
||||
'deliver_address': u'安徽省合肥市蜀山区望江西路666号',
|
||||
'description': u'车间一线员工劳保手套补充采购。',
|
||||
'status': '9',
|
||||
'discount_amount': 0,
|
||||
'items': [
|
||||
{'material_code': 'IT00005', 'cnt': 1000, 'sale_price': 5.50},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def load_samples():
|
||||
partner = Partner.objects.filter(partner_type='C').first()
|
||||
if not partner:
|
||||
print(u'错误: 未找到客户类型合作伙伴,请先维护基础数据。')
|
||||
return
|
||||
|
||||
user = User.objects.filter(username='chengcai').first() or User.objects.first()
|
||||
org = Organization.objects.first()
|
||||
creator = user.username if user else 'system'
|
||||
today = datetime.date.today()
|
||||
|
||||
created = 0
|
||||
skipped = 0
|
||||
|
||||
with transaction.atomic():
|
||||
for data in SAMPLE_ORDERS:
|
||||
if SaleOrder.objects.filter(code=data['code']).exists():
|
||||
print(u'跳过已存在订单: %s' % data['code'])
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
order = SaleOrder(
|
||||
code=data['code'],
|
||||
title=data['title'],
|
||||
order_date=data['order_date'],
|
||||
deliver_date=data['deliver_date'],
|
||||
partner=partner,
|
||||
org=org,
|
||||
user=user,
|
||||
contact=data['contact'],
|
||||
phone=data['phone'],
|
||||
deliver_address=data['deliver_address'],
|
||||
description=data['description'],
|
||||
invoice_type='10',
|
||||
status=data['status'],
|
||||
discount_amount=data['discount_amount'],
|
||||
amount=0,
|
||||
begin=today,
|
||||
end=datetime.date(9999, 12, 31),
|
||||
creator=creator,
|
||||
modifier=creator,
|
||||
)
|
||||
order.save()
|
||||
|
||||
for item_data in data['items']:
|
||||
material = Material.objects.filter(code=item_data['material_code']).first()
|
||||
if not material:
|
||||
print(u'警告: 物料 %s 不存在,已跳过' % item_data['material_code'])
|
||||
continue
|
||||
item = SaleItem(
|
||||
master=order,
|
||||
material=material,
|
||||
cnt=item_data['cnt'],
|
||||
sale_price=item_data['sale_price'],
|
||||
tax='0.00',
|
||||
)
|
||||
item.save()
|
||||
|
||||
if data['discount_amount'] > 0:
|
||||
order.save()
|
||||
total = recalc_order_amount(order)
|
||||
print(u'已创建: %s %s 金额=%s 状态=%s' % (
|
||||
order.code, order.title, total, order.get_status_display()
|
||||
))
|
||||
created += 1
|
||||
|
||||
print(u'\n完成: 新增 %d 条,跳过 %d 条。' % (created, skipped))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
load_samples()
|
||||
@@ -0,0 +1,4 @@
|
||||
Django==1.10.6
|
||||
PyMySQL==0.7.9
|
||||
xlwt==1.0.0
|
||||
xlrd==1.2.0
|
||||
@@ -0,0 +1,127 @@
|
||||
# 智捷ERP (SmartJet ERP) 操作手册
|
||||
|
||||
智捷ERP 是一款基于 Django 1.10.6 深度定制的企业资源计划 (ERP) 管理软件。系统经过现代 UI 重构与 Docker 化部署优化,提供销售管理、采购管理、库存管理、组织架构、人力资源及自动化工作流审批等核心功能。
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 1. 快速开始
|
||||
|
||||
### 1.1 环境要求
|
||||
- Docker
|
||||
- Docker Compose
|
||||
|
||||
### 1.2 一键部署
|
||||
在项目根目录下执行以下命令,即可启动 Web 服务与 MySQL 数据库:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.full.yml up -d --build
|
||||
```
|
||||
|
||||
### 1.3 访问地址
|
||||
- **后台管理**: [http://localhost:6010/admin/](http://localhost:6010/admin/)
|
||||
- **数据库服务**: `localhost:6011` (容器内 3306)
|
||||
|
||||
### 1.4 默认账号
|
||||
系统预置了管理员账号用于演示:
|
||||
|
||||
| 角色 | 用户名 | 密码 | 说明 |
|
||||
|------|--------|------|------|
|
||||
| 超级管理员 | xinmi_admin | admin123456 | 全新创建的超级用户,建议首选使用 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心功能模块
|
||||
|
||||
智捷ERP 采用现代化的“侧边栏 + 高密度工作台”布局,确保业务数据直观可见。
|
||||
|
||||

|
||||
|
||||
### 2.1 仪表盘 (Dashboard)
|
||||
**路径**: `/admin/`
|
||||
**功能概述**: 实时展现企业核心经营指标。
|
||||
- **数据看板**: 销售总额、待办审批、库存预警、系统在线率。
|
||||
- **趋势分析**: 关键指标下方的 Sparkline 动态折线图。
|
||||
- **快捷操作**: 一键新建销售单、采购入库、员工入职。
|
||||
|
||||
### 2.2 核心业务
|
||||
- **销售管理**: `/admin/sale/saleorder/` —— 涵盖报价单、销售订单、收款计划跟踪。
|
||||
- **采购管理**: `/admin/purchase/purchaseorder/` —— 支持采购申请、订单审批、入库确认。
|
||||
- **库存管理**: `/admin/invent/inventory/` —— 实时盘点、低水位报警、批次溯源。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 2.3 组织与人力
|
||||
- **组织架构**: `/admin/organ/organization/` —— 维护企业多级部门与岗位树。
|
||||
- **员工档案**: `/admin/basedata/employee/` —— 员工全生命周期管理、五险一金基础配置。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 2.4 流程协同
|
||||
- **审批中心**: `/admin/workflow/instance/` —— 流程实例监控。
|
||||
|
||||
- **待办任务**: `/admin/workflow/todolist/` —— 个人审批任务列表,支持紧急程度分级。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3. 视觉与体验优化 (Next-Gen UI 4.0)
|
||||
|
||||
本项目已完成 1:1 高保真 UI 复刻,具有以下设计特征:
|
||||
- **高级配色**: 采用 Indigo & Slate 商务配色。
|
||||
- **极致紧凑**: Ultra-Compact 模式,单屏展示数据量提升 40% 以上。
|
||||
- **响应式布局**: 侧边栏固定,超宽表格自动开启内部横向滚动保护。
|
||||
|
||||
---
|
||||
|
||||
## 4. 技术维护
|
||||
|
||||
### 4.1 数据库初始化
|
||||
数据库初始化 SQL 位于 `Install/SQL/mis.sql`,Docker 启动时会自动挂载并执行。
|
||||
|
||||
### 4.2 静态资源更新
|
||||
若修改了 CSS 或 JS,请在容器内执行:
|
||||
```bash
|
||||
docker compose exec web python manage.py collectstatic --noinput
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见问题 (FAQ)
|
||||
|
||||
**Q: 登录时提示“请输入正确的用户名和密码”?**
|
||||
A: 可能是由于默认加密算法版本差异,请通过控制台重置:
|
||||
`docker compose exec web python manage.py changepassword admin`
|
||||
|
||||
**Q: 列表页面字段太多,显示超宽?**
|
||||
A: 系统已开启横向滚动条保护,您可以在表格区域内左右滑动查看完整数据。
|
||||
|
||||
---
|
||||
|
||||
© 2026 智捷ERP (SmartJet ERP) · 让企业管理更智能
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
default_app_config = 'basedata.apps.BaseDataConfig'
|
||||
@@ -0,0 +1,339 @@
|
||||
# coding=utf-8
|
||||
from django.contrib import admin
|
||||
from django.forms import models
|
||||
from django.forms import fields,TextInput,Textarea
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||
from common import generic
|
||||
from basedata.models import ValueList,ValueListItem,Address,Partner,BankAccount,Project,Measure,Material,Brand,\
|
||||
Category,Warehouse,TechnicalParameterName,TechnicalParameterValue,Trade,ExpenseAccount,Employee,Family,Education,\
|
||||
WorkExperience,ExtraParam,DataImport,Document
|
||||
|
||||
|
||||
class ValueListItemInline(admin.TabularInline):
|
||||
model = ValueListItem
|
||||
exclude = ['group_code']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 3
|
||||
|
||||
|
||||
class ValueListAdmin(generic.BOAdmin):
|
||||
CODE_NUMBER_WIDTH = 3
|
||||
CODE_PREFIX = 'S'
|
||||
list_display = ['code', 'name', 'module', 'status']
|
||||
fields = (('code',),('name',),('module',),('status','init','locked',),('locked_by','lock_time',))
|
||||
raw_id_fields = ['module']
|
||||
readonly_fields = ['locked_by','lock_time']
|
||||
inlines = [ValueListItemInline]
|
||||
search_fields = ['code','name']
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(ValueListAdmin,self).save_model(request,obj,form,change)
|
||||
obj.valuelistitem_set.update(group_code=obj.code)
|
||||
|
||||
|
||||
class AddressAdmin(generic.BOAdmin):
|
||||
list_display = ['address','phone','contacts']
|
||||
exclude = ['content_type','object_id','creator','modifier','creation','modification','begin','end']
|
||||
|
||||
|
||||
class AddressInline(GenericTabularInline):
|
||||
model = Address
|
||||
exclude = ['content_type','object_id','creator','modifier','creation','modification','begin','end']
|
||||
extra = 1
|
||||
|
||||
|
||||
class BankAccountInline(admin.TabularInline):
|
||||
model = BankAccount
|
||||
fields = ['account','title','memo']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
class PartnerForm(models.ModelForm):
|
||||
tax_address = fields.CharField(widget=TextInput(attrs={'size': 119,}),required=False,label=_("tax address"))
|
||||
memo = fields.CharField(widget=Textarea(attrs={'rows':3,'cols':85}),required=False,label=_("memo"))
|
||||
|
||||
class Meta:
|
||||
model = Partner
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PartnerAdmin(generic.BOAdmin):
|
||||
list_display = ['code','name','partner_type','level']
|
||||
list_display_links = ['code','name']
|
||||
|
||||
fields = (('code','name',),('short','pinyin',),('partner_type','level'),('tax_num','tax_account',),
|
||||
('tax_address',),('contacts','phone',),('memo',),)
|
||||
search_fields = ['code','name','pinyin']
|
||||
form = PartnerForm
|
||||
save_on_top = True
|
||||
inlines = [AddressInline,BankAccountInline]
|
||||
|
||||
def get_queryset(self, request):
|
||||
if request.user.is_superuser or (request.user.has_perm('basedate.view_all_customer') and request.user.has_perm('basedate.view_all_supplier')):
|
||||
return super(PartnerAdmin,self).get_queryset(request)
|
||||
elif request.user.has_perm('basedata.view_all_customer'):
|
||||
return super(PartnerAdmin,self).get_queryset(request).filter(partner_type='C')
|
||||
else:
|
||||
return super(PartnerAdmin,self).get_queryset(request).filter(partner_type='S')
|
||||
|
||||
|
||||
class ProjectForm(models.ModelForm):
|
||||
income = fields.DecimalField(required=False,widget=TextInput(attrs={'readonly':'true'}))
|
||||
expand = fields.DecimalField(required=False,widget=TextInput(attrs={'readonly':'true'}))
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ProjectAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'PJ'
|
||||
list_display = ['code','name','status','income','expand']
|
||||
list_display_links = ['code','name']
|
||||
fields = (
|
||||
('code','name',),('short','pinyin',),
|
||||
('partner',),('status','prj_type',),
|
||||
('description',),
|
||||
('budget','income','expand',),('blueprint',),('offer',),('business',),('users',),
|
||||
)
|
||||
search_fields = ['code','name']
|
||||
readonly_fields = ['status']
|
||||
raw_id_fields = ['partner']
|
||||
filter_horizontal = ['users']
|
||||
form = ProjectForm
|
||||
|
||||
|
||||
class WarehouseAdmin(admin.ModelAdmin):
|
||||
list_display = ['code','name','location']
|
||||
filter_horizontal = ['users']
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(WarehouseAdmin,self).save_model(request,obj,form,change)
|
||||
try:
|
||||
code = getattr(obj,'code')
|
||||
if not code:
|
||||
obj.code = '%s%02d' % ('A',obj.id)
|
||||
obj.save()
|
||||
except Exception,e:
|
||||
self.message_user(request,'ERROR:%s' % e,level=messages.ERROR)
|
||||
|
||||
|
||||
class BrandAdmin(admin.ModelAdmin):
|
||||
list_display = ['name','pinyin']
|
||||
|
||||
|
||||
class MeasureAdmin(admin.ModelAdmin):
|
||||
list_display = ['code','name','status']
|
||||
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ['code','name','path']
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(CategoryAdmin,self).save_model(request,obj,form,change)
|
||||
try:
|
||||
code = getattr(obj,'code')
|
||||
if not code:
|
||||
obj.code = '%s%02d' % ('F',obj.id)
|
||||
obj.save()
|
||||
if obj.parent:
|
||||
if obj.parent.path:
|
||||
obj.path = obj.parent.path + '/'+obj.parent.name
|
||||
else:
|
||||
obj.path = obj.parent.name
|
||||
obj.save()
|
||||
except Exception,e:
|
||||
self.message_user(request,'ERROR:%s' % e,level=messages.ERROR)
|
||||
|
||||
|
||||
class MaterialForm(models.ModelForm):
|
||||
name = fields.CharField(widget=TextInput(attrs={"size":"119"}),label=_("material name"))
|
||||
spec = fields.CharField(widget=TextInput(attrs={"size":"119"}),required=False,label=_("specifications"))
|
||||
|
||||
class Mata:
|
||||
model = Material
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ExtraParamInline(admin.TabularInline):
|
||||
model = ExtraParam
|
||||
fields = ['name','data_type','data_source']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
class MaterialAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'IT'
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
list_display = ['code','name','spec','tp']
|
||||
list_display_links = ['code','name']
|
||||
list_filter = ['brand','tp']
|
||||
search_fields = ['code','name']
|
||||
fields = (
|
||||
('code','barcode'),('name',),('spec',),
|
||||
('brand',),('category',),('status','is_equip','can_sale','is_virtual',),
|
||||
('warehouse',),('tp',),('measure',),('stock_price','purchase_price','sale_price',),
|
||||
)
|
||||
filter_horizontal = ['measure']
|
||||
inlines = [ExtraParamInline]
|
||||
form = MaterialForm
|
||||
|
||||
|
||||
class TechParamValueInline(admin.TabularInline):
|
||||
model = TechnicalParameterValue
|
||||
|
||||
|
||||
class TechParamNameAdmin(admin.ModelAdmin):
|
||||
list_display = ['name','category']
|
||||
inlines = [TechParamValueInline]
|
||||
|
||||
|
||||
class TradeAdmin(admin.ModelAdmin):
|
||||
list_display = ['code','name','parent']
|
||||
|
||||
|
||||
class ExpenseAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'FC'
|
||||
list_display = ['code','name','category']
|
||||
list_display_links = ['code','name']
|
||||
list_filter = ['category']
|
||||
search_fields = ['name']
|
||||
|
||||
|
||||
class FamilyForm(models.ModelForm):
|
||||
name = fields.CharField(widget=TextInput(attrs={"size":"25"}),label=_("name"))
|
||||
phone = fields.CharField(widget=TextInput(attrs={"size":"25"}),label=_("phone"))
|
||||
|
||||
class Meta:
|
||||
model = Family
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FamilyInline(admin.TabularInline):
|
||||
model = Family
|
||||
exclude = ['creator','modifier','creation','modification','begin','end']
|
||||
form = FamilyForm
|
||||
extra = 1
|
||||
|
||||
|
||||
class EducationInline(admin.TabularInline):
|
||||
model = Education
|
||||
exclude = ['creator','modifier','creation','modification']
|
||||
extra = 0
|
||||
|
||||
|
||||
class WorkExperienceInline(admin.TabularInline):
|
||||
model = WorkExperience
|
||||
exclude = ['creator','modifier','creation','modification']
|
||||
extra = 1
|
||||
|
||||
|
||||
class EmployeeAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = '1'
|
||||
list_display = ['code','name','position','gender','idcard','age','work_age','literacy','phone','email']
|
||||
search_fields = ['code','name','idcard','pinyin']
|
||||
fieldsets = [
|
||||
(None,{'fields':[('code','phone',),('name','pinyin',),('gender','birthday',),('idcard','country',),
|
||||
('position',),('rank','category'),('status','ygxs',),('workday','startday',)]}),
|
||||
(_('other info'),{'fields':[('hometown','address',),('banknum','bankname',),('email','office',),
|
||||
('emergency','literacy',),('religion','marital',),('party','nation',),('spjob','health',),
|
||||
('major','degree',),('tag1','tag2',),('tag3','tag4',),('user',),],'classes':['collapse']}),
|
||||
]
|
||||
readonly_fields = ['status','ygxs','rank','category']
|
||||
inlines = [FamilyInline,EducationInline,WorkExperienceInline]
|
||||
raw_id_fields = ['user']
|
||||
|
||||
def get_queryset(self, request):
|
||||
if request.user.is_superuser or request.user.has_perm('basedata.view_all_employee'):
|
||||
return super(EmployeeAdmin,self).get_queryset(request)
|
||||
else:
|
||||
return super(EmployeeAdmin,self).get_queryset(request).filter(user=request.user)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if request.user.is_superuser:
|
||||
return []
|
||||
else:
|
||||
return ['status','ygxs','rank','category','position','user']
|
||||
|
||||
|
||||
class DataImportAdmin(generic.BOAdmin):
|
||||
list_display = ['imp_date','title','status']
|
||||
list_display_links = ['imp_date','title']
|
||||
raw_id_fields = ['content_type']
|
||||
readonly_fields = ['status']
|
||||
extra_buttons = [{'href':'action','title':_('import')}]
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
if object_id:
|
||||
obj = DataImport.objects.get(id=object_id)
|
||||
if obj.status == '1':
|
||||
extra_context = extra_context or {}
|
||||
extra_context.update(dict(readonly=True))
|
||||
return super(DataImportAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
|
||||
class DocumentForm(models.ModelForm):
|
||||
title = fields.CharField(widget=TextInput(attrs={"size":"119"}),label=_("title"))
|
||||
keywords = fields.CharField(widget=TextInput(attrs={"size":"119"}),label=_("keywords"))
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DocumentAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'FD'
|
||||
CODE_NUMBER_WIDTH = 4
|
||||
list_display = ['code','title','keywords','tp','business_domain','status','creation']
|
||||
list_display_links = ['code','title']
|
||||
fields = (('code','status',),('title',),('keywords',),('description',),('business_domain','tp',),('attach',))
|
||||
readonly_fields = ['status']
|
||||
list_filter = ['tp','business_domain']
|
||||
search_fields = ['title','keywords','code']
|
||||
form = DocumentForm
|
||||
actions = ['publish']
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj and obj.status=='1':
|
||||
return ['code','status','title','keywords','description','business_domain','tp','attach',]
|
||||
else:
|
||||
return ['status']
|
||||
|
||||
def publish(self,request,queryset):
|
||||
import datetime
|
||||
cnt = queryset.filter(status='0').update(status='1',pub_date=datetime.datetime.now())
|
||||
self.message_user(request,u'%s 个文档发布成功'%cnt)
|
||||
|
||||
publish.short_description = _('publish selected %(verbose_name_plural)s')
|
||||
|
||||
# admin.site.register(Address,AddressAdmin)
|
||||
admin.site.register(ValueList,ValueListAdmin)
|
||||
admin.site.register(Partner,PartnerAdmin)
|
||||
admin.site.register(Project,ProjectAdmin)
|
||||
admin.site.register(Material,MaterialAdmin)
|
||||
admin.site.register(Warehouse,WarehouseAdmin)
|
||||
admin.site.register(Brand,BrandAdmin)
|
||||
admin.site.register(Measure,MeasureAdmin)
|
||||
admin.site.register(Category,CategoryAdmin)
|
||||
admin.site.register(TechnicalParameterName,TechParamNameAdmin)
|
||||
admin.site.register(Trade,TradeAdmin)
|
||||
admin.site.register(ExpenseAccount,ExpenseAdmin)
|
||||
admin.site.register(Employee,EmployeeAdmin)
|
||||
admin.site.register(DataImport,DataImportAdmin)
|
||||
admin.site.register(Document,DocumentAdmin)
|
||||
@@ -0,0 +1,9 @@
|
||||
__author__ = 'zhugl'
|
||||
# created at 15-4-22
|
||||
from django.apps.config import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class BaseDataConfig(AppConfig):
|
||||
name = 'basedata'
|
||||
verbose_name = _('BaseData')
|
||||
@@ -0,0 +1,657 @@
|
||||
# coding=utf-8
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.text import force_text
|
||||
from common import const
|
||||
from common import generic
|
||||
from syscfg.models import Module,Site
|
||||
from organ.models import Organization,Position
|
||||
import datetime
|
||||
from plugin.xls import ExcelManager
|
||||
|
||||
|
||||
class ValueList(generic.BO):
|
||||
"""
|
||||
值列表
|
||||
"""
|
||||
index_weight = 9
|
||||
code = models.CharField(_("list code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||
name = models.CharField(_("list name"),max_length=const.DB_CHAR_NAME_40)
|
||||
module = models.ForeignKey(Module,verbose_name=_("module"),blank=True,null=True)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
init = models.BooleanField(_("is init"),default=False)
|
||||
locked = models.BooleanField(_("is locked"),default=False)
|
||||
locked_by = models.ForeignKey(User,verbose_name=_("locked by"),blank=True,null=True)
|
||||
lock_time = models.DateTimeField(_("locked time"),null=True,blank=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
super(ValueList,self).save(force_insert,force_update,using,update_fields)
|
||||
sql = 'update basedata_valuelistitem set group_code = %s where groud_id=%s'
|
||||
params = [self.code,self.id]
|
||||
generic.update(sql,params)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('value list')
|
||||
verbose_name_plural = _('value list')
|
||||
|
||||
|
||||
class ValueListItem(models.Model):
|
||||
"""
|
||||
值列表项
|
||||
"""
|
||||
group = models.ForeignKey(ValueList,verbose_name=_("list group"))
|
||||
group_code = models.CharField(max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||
code = models.CharField(_("item code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||
name = models.CharField(_("item name"),max_length=const.DB_CHAR_NAME_40)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
weight = models.IntegerField(_("weight"),null=True,default=9)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if not self.code:
|
||||
cnt = self.group.valuelistitem_set.count()+1
|
||||
self.code = "%02d" % cnt
|
||||
self.group_code = self.group.code
|
||||
super(ValueListItem,self).save(force_insert,force_update,using,update_fields)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s-%s" % (self.code,self.name)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('list item')
|
||||
verbose_name_plural = _('list item')
|
||||
ordering = ['weight','code']
|
||||
index_together = ['group','group_code']
|
||||
|
||||
|
||||
def get_value_list(group):
|
||||
"""
|
||||
|
||||
:param group:
|
||||
:return:
|
||||
"""
|
||||
if group:
|
||||
return tuple([(item.code, item.name) for item in ValueListItem.objects.filter(group_code__exact=group,status=1)])
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class Address(generic.BO):
|
||||
"""
|
||||
地址
|
||||
"""
|
||||
ADDRESS_TYPE = get_value_list('S011')
|
||||
address_type = models.CharField(_("address type"),max_length=const.DB_CHAR_CODE_2,choices=ADDRESS_TYPE,default='01')
|
||||
address = models.CharField(_("address"),max_length=const.DB_CHAR_NAME_120)
|
||||
zipcode = models.CharField(_("zipcode"),max_length=const.DB_CHAR_CODE_8,blank=True,null=True)
|
||||
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
contacts = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
|
||||
content_type = models.ForeignKey(ContentType,blank=True,null=True)
|
||||
object_id = models.PositiveIntegerField(blank=True,null=True)
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('address')
|
||||
verbose_name_plural = _('address')
|
||||
|
||||
|
||||
class Partner(generic.BO):
|
||||
"""
|
||||
合作伙伴
|
||||
"""
|
||||
index_weight = 3
|
||||
PARTNER_TYPE = (
|
||||
('C', _('Customer')),
|
||||
('S', _('Supplier')),
|
||||
)
|
||||
|
||||
LEVEL = (
|
||||
('A','A'),
|
||||
('B','B'),
|
||||
('C','C'),
|
||||
('D','D'),
|
||||
)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
code = models.CharField(_("partner code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
name = models.CharField(_("partner name"),max_length=const.DB_CHAR_NAME_120)
|
||||
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
partner_type = models.CharField(_("type"),max_length=const.DB_CHAR_CODE_2,choices=PARTNER_TYPE,default='C')
|
||||
level = models.CharField(_("level"),max_length=const.DB_CHAR_CODE_2,choices=LEVEL,default='C')
|
||||
|
||||
tax_num = models.CharField(_("tax num"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
tax_address = models.CharField(_("tax address"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
tax_account = models.CharField(_("tax account"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
|
||||
contacts = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
memo = models.TextField(_("memo"),blank=True,null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('partner')
|
||||
verbose_name_plural = _('partner')
|
||||
permissions = (
|
||||
('view_all_customer',_("view all customer")),
|
||||
('view_all_supplier',_("view all supplier")),
|
||||
)
|
||||
|
||||
|
||||
class BankAccount(generic.BO):
|
||||
"""
|
||||
银行账户 organization
|
||||
"""
|
||||
account = models.CharField(_("account num"),max_length=const.DB_CHAR_NAME_40)
|
||||
title = models.CharField(_("bank name"),max_length=const.DB_CHAR_NAME_40)
|
||||
memo = models.CharField(_("memo"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
partner = models.ForeignKey(Partner,verbose_name=_("partner"),blank=True,null=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
name = ''
|
||||
if self.org:
|
||||
name = self.org.name
|
||||
elif self.partner:
|
||||
name = self.partner.name
|
||||
return u"%s %s %s" % (name,self.account,self.title)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('bank account')
|
||||
verbose_name_plural = _('bank account')
|
||||
|
||||
|
||||
class Project(generic.BO):
|
||||
"""
|
||||
工程项目
|
||||
"""
|
||||
STATUS = get_value_list('S012')
|
||||
TYPES = get_value_list('S013')
|
||||
index_weight = 1
|
||||
|
||||
code = models.CharField(_("project code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
name = models.CharField(_("project name"),max_length=const.DB_CHAR_NAME_120)
|
||||
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
|
||||
partner = models.ForeignKey(Partner,blank=True,null=True,verbose_name=_("partner"),limit_choices_to={"partner_type":"C"})
|
||||
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,default='00',choices=STATUS)
|
||||
prj_type = models.CharField(_("project type"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=TYPES,default='00')
|
||||
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
|
||||
budget = models.DecimalField(_("budget"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
income = models.DecimalField(_("income"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
expand = models.DecimalField(_("expand"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
|
||||
blueprint = models.FileField(_("blueprint"),upload_to='project',blank=True,null=True)
|
||||
offer = models.FileField(_("offer sheet"),upload_to='offer sheet',blank=True,null=True)
|
||||
business = models.FileField(_("business document"),upload_to='project',blank=True,null=True)
|
||||
|
||||
users = models.ManyToManyField(User,verbose_name=_("related users"),blank=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('project')
|
||||
verbose_name_plural = _('project')
|
||||
|
||||
|
||||
class Warehouse(models.Model):
|
||||
"""
|
||||
仓库
|
||||
"""
|
||||
index_weight = 6
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_40)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
location = models.CharField(_("location"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
users = models.ManyToManyField(User,verbose_name=_("related users"),blank=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('warehouse')
|
||||
verbose_name_plural = _('warehouse')
|
||||
|
||||
|
||||
class Measure(models.Model):
|
||||
"""
|
||||
计量单位
|
||||
"""
|
||||
index_weight = 5
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_20)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('measure')
|
||||
verbose_name_plural = _('measure')
|
||||
|
||||
|
||||
class Trade(models.Model):
|
||||
"""
|
||||
国民经济行业分类
|
||||
"""
|
||||
index_weight = 102
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_6)
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||
memo = models.CharField(_("memo"),max_length=const.DB_CHAR_NAME_120,null=True,blank=True)
|
||||
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('trade')
|
||||
verbose_name_plural = _('trade')
|
||||
ordering = ['code']
|
||||
|
||||
|
||||
class Brand(models.Model):
|
||||
"""
|
||||
品牌
|
||||
"""
|
||||
index_weight = 101
|
||||
trade = models.ForeignKey(Trade,verbose_name=_("trade"),null=True,blank=True)
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
weight = models.IntegerField(_("weight"),blank=True,null=True,default=99)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('brand')
|
||||
verbose_name_plural = _('brand')
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
"""
|
||||
分类
|
||||
"""
|
||||
index_weight = 100
|
||||
trade = models.ForeignKey(Trade,verbose_name=_("trade"),null=True,blank=True)
|
||||
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_6,null=True,blank=True)
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||
path = models.CharField(_("path"),max_length=const.DB_CHAR_NAME_200,null=True,blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('category')
|
||||
verbose_name_plural = _('category')
|
||||
|
||||
|
||||
class TechnicalParameterName(models.Model):
|
||||
"""
|
||||
技术参数-名称,将技术参数绑定于物料分类上,在此分类下的物料自动继承全部技术参数
|
||||
"""
|
||||
index_weight = 7
|
||||
category = models.ForeignKey(Category,verbose_name=_("material category"))
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_40)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('technical parameter')
|
||||
verbose_name_plural = _('technical parameter')
|
||||
|
||||
|
||||
class TechnicalParameterValue(models.Model):
|
||||
"""
|
||||
技术参数-值,将技术参数绑定于物料分类上,在此分类下的物料自动继承全部技术参数
|
||||
"""
|
||||
tech_name = models.ForeignKey(TechnicalParameterName,verbose_name=_("technical name"))
|
||||
value = models.CharField(_("value"),max_length=const.DB_CHAR_NAME_80)
|
||||
description = models.CharField(_("description"),max_length=const.DB_CHAR_NAME_80,null=True,blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.value
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('technical value')
|
||||
verbose_name_plural = _('technical value')
|
||||
|
||||
|
||||
class Material(generic.BO):
|
||||
"""
|
||||
物料
|
||||
"""
|
||||
index_weight = 4
|
||||
code = models.CharField(_("material code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
barcode = models.CharField(_("bar code"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
name = models.CharField(_("material name"),max_length=const.DB_CHAR_NAME_120)
|
||||
spec = models.CharField(_("specifications"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
brand = models.ForeignKey(Brand,blank=True,null=True,verbose_name=_("brand"))
|
||||
category = models.ForeignKey(Category,blank=True,null=True,verbose_name=_("category"))
|
||||
tp = models.CharField(_('mt type'),blank=True,null=True,max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S054'),default='10')
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
is_equip = models.BooleanField(_("is equipment"),default=False)
|
||||
can_sale = models.BooleanField(_("can sale"),default=True)
|
||||
is_virtual = models.BooleanField(_("is virtual"),default=False)
|
||||
|
||||
warehouse = models.ForeignKey(Warehouse,blank=True,null=True,verbose_name=_("warehouse"))
|
||||
measure = models.ManyToManyField(Measure,verbose_name=_("measure"))
|
||||
|
||||
params = models.ManyToManyField(TechnicalParameterValue,verbose_name=_("technical parameter"),through='MaterialParam')
|
||||
|
||||
stock_price = models.DecimalField(_("stock price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
purchase_price = models.DecimalField(_("purchase price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
sale_price = models.DecimalField(_("sale price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
|
||||
return "%s %s" % (self.code,self.name)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('material')
|
||||
verbose_name_plural = _('material')
|
||||
ordering = ['tp','code']
|
||||
|
||||
|
||||
class MaterialParam(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
material = models.ForeignKey(Material)
|
||||
param_value = models.ForeignKey(TechnicalParameterValue)
|
||||
param_name = models.ForeignKey(TechnicalParameterName,blank=Trade,null=True)
|
||||
creation = models.DateField(auto_now_add=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % self.param_value
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('material parameter')
|
||||
verbose_name_plural = _('material parameter')
|
||||
|
||||
|
||||
class ExtraParam(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
DATA_TYPE = (
|
||||
('CHAR',_('CHAR')),
|
||||
('NUM',_('NUMBER')),
|
||||
('DATE',_('DATE')),
|
||||
)
|
||||
material = models.ForeignKey(Material,verbose_name=_("material"))
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_40)
|
||||
data_type = models.CharField(_("data type"),default='CHAR',choices=DATA_TYPE,max_length=const.DB_CHAR_CODE_6)
|
||||
data_source = models.CharField(_("data source"),blank=True,null=True,max_length=const.DB_CHAR_NAME_40)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s" % self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("extra param")
|
||||
verbose_name_plural = _("extra params")
|
||||
|
||||
|
||||
class ExpenseAccount(generic.BO):
|
||||
"""
|
||||
费用科目
|
||||
"""
|
||||
CATEGORY = (
|
||||
('HR',_('HR-DOMAIN')),
|
||||
('OF',_('OFFICE-DOMAIN')),
|
||||
('PU',_('PUBLIS-DOMAIN')),
|
||||
('MU',_('MUNADOMAIN')),
|
||||
('BU',_('BUSINESS')),
|
||||
('OT',_('OTHER')),
|
||||
)
|
||||
index_weight = 10
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||
category = models.CharField(_("category"),max_length=const.DB_CHAR_CODE_4,choices=CATEGORY,default='PU')
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('expenses account')
|
||||
verbose_name_plural = _('expenses account')
|
||||
ordering = ['category','code']
|
||||
|
||||
|
||||
class Employee(generic.BO):
|
||||
"""
|
||||
职员信息
|
||||
"""
|
||||
index_weight = 2
|
||||
code = models.CharField(_("employee number"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
organization = models.ForeignKey(Organization,verbose_name = _('organization'),null=True,blank=True)
|
||||
name = models.CharField(_("employee name"),max_length=const.DB_CHAR_NAME_120)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
birthday = models.DateField(_("birthday"),blank=True,null=True)
|
||||
|
||||
gender = models.CharField(_("gender"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('gender'),default='1')
|
||||
idcard = models.CharField(_("id card"),max_length=const.DB_CHAR_NAME_20)
|
||||
|
||||
country = models.CharField(_("nationality"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,default='CN',choices=const.get_value_list('S022'))
|
||||
hometown = models.CharField(_("hometown"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
address = models.CharField(_("home address"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
banknum = models.CharField(_("bank account"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
bankname = models.CharField(_("bank name"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
emergency = models.CharField(_("emergency contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
email = models.CharField(_("email"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
office = models.CharField(_("office phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
|
||||
position = models.ForeignKey(Position,verbose_name = _('position'))
|
||||
rank = models.CharField(_("employee rank"),max_length=const.DB_CHAR_CODE_2,default='00',choices=const.get_value_list('S017'))
|
||||
|
||||
workday = models.DateField(_("workday"),blank=True,null=True)
|
||||
startday = models.DateField(_("start date"),blank=True,null=True)
|
||||
|
||||
religion = models.CharField(_("religion"),max_length=const.DB_CHAR_CODE_2,default='00',choices=const.get_value_list('S020'),blank=True,null=True,)
|
||||
marital = models.CharField(_("marital status"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S023'),default='10')
|
||||
|
||||
party = models.CharField(_("political party"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S026'),default='13')
|
||||
nation = models.CharField(_("nation"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S021'),default='01')
|
||||
|
||||
ygxs = models.CharField(_("employ ygxs"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S019'),default='2')
|
||||
status = models.CharField(_("employ status"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S016'),default='10')
|
||||
category = models.CharField(_("employ category"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S018'),default='21')
|
||||
|
||||
literacy = models.CharField(_("literacy"),max_length=const.DB_CHAR_CODE_2,default='10',choices=const.get_value_list('S024'),blank=True,null=True)
|
||||
major = models.CharField(_("major type"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S038'),default='99')
|
||||
degree = models.CharField(_("major degree"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S037'),default='4')
|
||||
|
||||
spjob = models.CharField(_("special job"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S042'),default='00')
|
||||
health = models.CharField(_("health"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S043'),default='1')
|
||||
|
||||
tag1 = models.CharField(_("tag1 fzjr"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S039'),default='99')
|
||||
tag2 = models.CharField(_("tag2 dwld"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S040'),default='9')
|
||||
tag3 = models.CharField(_("tag3 dsjs"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S041'),default='00')
|
||||
tag4 = models.CharField(_("tag4 byzk"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S027'),default='0')
|
||||
|
||||
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||
|
||||
def age(self):
|
||||
import datetime
|
||||
if self.birthday:
|
||||
cnt = datetime.date.today().year-self.birthday.year
|
||||
return cnt
|
||||
|
||||
def work_age(self):
|
||||
import datetime
|
||||
if self.birthday and self.workday:
|
||||
cnt = datetime.date.today().year-self.workday.year
|
||||
return cnt
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s %s'%(self.code,self.name)
|
||||
|
||||
age.short_description = u'年龄'
|
||||
work_age.short_description = u'工龄'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("employee")
|
||||
verbose_name_plural = _("employee")
|
||||
permissions = (
|
||||
('view_all_employee',_("view all employee")),
|
||||
)
|
||||
|
||||
|
||||
class Family(generic.BO):
|
||||
"""
|
||||
家庭成员
|
||||
"""
|
||||
relation = models.CharField(_("family title"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S025'))
|
||||
status = models.CharField(_("social status"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S029'),default='17')
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_60)
|
||||
birthday = models.DateField(_("birthday"),blank=True,null=True)
|
||||
organization = models.CharField(_("organization"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
emergency = models.BooleanField(_("emergency"),default=False)
|
||||
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("family member")
|
||||
verbose_name_plural = _("family member")
|
||||
|
||||
|
||||
class Education(generic.BO):
|
||||
"""
|
||||
教育履历
|
||||
"""
|
||||
edu_type = models.CharField(_("edu type"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S035'),default='1')
|
||||
school = models.CharField(_("school"),max_length=const.DB_CHAR_NAME_120)
|
||||
major = models.CharField(_("major"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
degree = models.CharField(_("major degree"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S037'),default='4')
|
||||
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("education experience")
|
||||
verbose_name_plural = _("education experience")
|
||||
|
||||
|
||||
class WorkExperience(generic.BO):
|
||||
"""
|
||||
工作履历
|
||||
"""
|
||||
organization = models.CharField(_("organization"),max_length=const.DB_CHAR_NAME_120)
|
||||
position = models.CharField(_("position"),max_length=const.DB_CHAR_NAME_120)
|
||||
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("work experience")
|
||||
verbose_name_plural = _("work experience")
|
||||
|
||||
|
||||
class DataImport(generic.BO):
|
||||
|
||||
"""
|
||||
Data import
|
||||
"""
|
||||
actions = {}
|
||||
|
||||
STATUS = (
|
||||
('0',_('NEW')),
|
||||
('1',_('EXECUTED')),
|
||||
)
|
||||
imp_date = models.DateField(_('date'),blank=True,null=True,default=datetime.datetime.today)
|
||||
title = models.CharField(_('title'),max_length=const.DB_CHAR_NAME_40)
|
||||
description = models.TextField(_('description'),blank=True,null=True)
|
||||
content_type = models.ForeignKey(ContentType,verbose_name=_("content type"),limit_choices_to={"app_label__in":['basedata','organ','auth']})
|
||||
attach = models.FileField(_('attach'),blank=True,null=True,upload_to='data')
|
||||
is_clear = models.BooleanField(_('clear old data?'),default=0)
|
||||
handler = models.CharField(_('handler class'),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
status = models.CharField(_('status'),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
|
||||
def action_import(self,request):
|
||||
from django.db import transaction
|
||||
if self.attach:
|
||||
if self.handler:
|
||||
klass = ExcelManager().handlers.get(self.handler)
|
||||
with transaction.atomic():
|
||||
klass.handle(self,self.attach)
|
||||
self.status = 1
|
||||
self.save()
|
||||
else:
|
||||
import xlrd
|
||||
import os
|
||||
from mis import settings
|
||||
path = os.path.join(settings.MEDIA_ROOT,self.attach.name)
|
||||
workbook = xlrd.open_workbook(path)
|
||||
sheet = workbook.sheet_by_index(0)
|
||||
row_count = sheet.nrows
|
||||
col_count = sheet.ncols
|
||||
cols = []
|
||||
with transaction.atomic():
|
||||
for row_index in range(row_count):
|
||||
line = sheet.row_values(row_index)
|
||||
if row_index == 0:
|
||||
cols = line
|
||||
continue
|
||||
elif row_index == 1:
|
||||
continue
|
||||
else:
|
||||
klass = self.content_type.model_class()
|
||||
values = line
|
||||
params = {}
|
||||
for name in cols:
|
||||
index = cols.index(name)
|
||||
v = values[index]
|
||||
if type(v) == str:
|
||||
v = force_text(v.decode('gbk'))
|
||||
params[name]=v
|
||||
# print 'name is %s value is %s'%(name,v)
|
||||
try:
|
||||
params.pop('')
|
||||
except Exception,e:
|
||||
pass
|
||||
# print params
|
||||
klass.objects.create(**params)
|
||||
self.status = '1'
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("data import")
|
||||
verbose_name_plural = _("data import")
|
||||
|
||||
|
||||
class Document(generic.BO):
|
||||
"""
|
||||
文档管理
|
||||
"""
|
||||
TP = (
|
||||
('00',_('SYSTEM MANUAL')),
|
||||
('10',_('BUSINESS DOC')),
|
||||
)
|
||||
STATUS = (
|
||||
('0',_('draft')),
|
||||
('1',_('published'))
|
||||
)
|
||||
index_weight = 8
|
||||
code = models.CharField(_('code'),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
title = models.CharField(_('title'),max_length=const.DB_CHAR_NAME_120)
|
||||
keywords = models.CharField(_('keywords'),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
description = models.TextField(_('description'),blank=True,null=True)
|
||||
tp = models.CharField(_('type'),max_length=const.DB_CHAR_CODE_2,default='10',choices=TP)
|
||||
business_domain = models.CharField(_("business domain"),max_length=const.DB_CHAR_CODE_4,choices=const.get_value_list('S045'),default='OT')
|
||||
user = models.ForeignKey(User,verbose_name=_('user'),blank=True,null=True)
|
||||
status = models.CharField(_('status'),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
pub_date = models.DateTimeField(_('publish date'),blank=True,null=True)
|
||||
size = models.CharField(_('size'),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
attach = models.FileField(_('attach'),blank=True,null=True,upload_to='doc')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("document")
|
||||
verbose_name_plural = _("documents")
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.conf.urls import include, url,static
|
||||
import basedata.views
|
||||
|
||||
urlpatterns = [
|
||||
url(r"dataimport/(?P<object_id>\d+)/action", basedata.views.action_import),
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
# coding=utf-8
|
||||
from django.contrib.admin import site
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.utils.encoding import force_text
|
||||
from django.template.response import TemplateResponse
|
||||
from django.contrib import messages
|
||||
from basedata.models import DataImport
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
def action_import(request,object_id):
|
||||
"""
|
||||
数据导入操作
|
||||
:param request:
|
||||
:param object_id:
|
||||
:return:
|
||||
"""
|
||||
title = _("Are you sure?")
|
||||
obj = DataImport.objects.get(id=int(object_id))
|
||||
opts = obj._meta
|
||||
objects_name = force_text(opts.verbose_name)
|
||||
|
||||
if request.POST.get("post"):
|
||||
obj.action_import(request)
|
||||
try:
|
||||
|
||||
messages.success(request,_('data import successfully'))
|
||||
except Exception,e:
|
||||
messages.error(request,e)
|
||||
|
||||
return HttpResponseRedirect("/admin/basedata/dataimport/%s"%(object_id))
|
||||
|
||||
context = dict(
|
||||
site.each_context(request),
|
||||
title=title,
|
||||
opts=opts,
|
||||
objects_name=objects_name,
|
||||
object=obj,
|
||||
)
|
||||
request.current_app = site.name
|
||||
|
||||
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||
@@ -0,0 +1 @@
|
||||
__author__ = 'Administrator'
|
||||
@@ -0,0 +1,39 @@
|
||||
# coding=utf-8
|
||||
from django.db import connection
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
__author__ = 'zhugl'
|
||||
|
||||
DB_CHAR_CODE_2 = 2
|
||||
DB_CHAR_CODE_4 = 4
|
||||
DB_CHAR_CODE_6 = 6
|
||||
DB_CHAR_CODE_8 = 8
|
||||
DB_CHAR_CODE_10 = 10
|
||||
|
||||
DB_CHAR_NAME_20 = 20
|
||||
DB_CHAR_NAME_40 = 40
|
||||
DB_CHAR_NAME_60 = 60
|
||||
DB_CHAR_NAME_80 = 80
|
||||
DB_CHAR_NAME_120 = 120
|
||||
DB_CHAR_NAME_200 = 200
|
||||
|
||||
|
||||
STATUS_ON_OFF = (
|
||||
(0,_('OFF')),
|
||||
(0,_('ON')),
|
||||
)
|
||||
|
||||
|
||||
def get_value_list(group):
|
||||
"""
|
||||
获取值列表信息
|
||||
"""
|
||||
if group:
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT code,name FROM basedata_valuelistitem WHERE group_code=%s AND status=1',[group])
|
||||
rows = cursor.fetchall()
|
||||
return tuple([(code,name) for code,name in rows])
|
||||
except Exception,e:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
@@ -0,0 +1,312 @@
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
# created at 15-4-21
|
||||
import datetime
|
||||
import xlwt
|
||||
import re
|
||||
from django.db import models
|
||||
from django.db import connection,transaction
|
||||
from django.db.models import fields
|
||||
from django.db.models.fields import related
|
||||
from django.contrib import admin
|
||||
from django.http import HttpRequest,HttpResponseRedirect,HttpResponse
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.text import force_text
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.http import urlquote
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import const
|
||||
from midware import cuser
|
||||
|
||||
|
||||
def update(sql, params=None):
|
||||
"""
|
||||
:param sql:
|
||||
:param params:
|
||||
:return:
|
||||
"""
|
||||
cursor = connection.cursor()
|
||||
with transaction.atomic():
|
||||
try:
|
||||
if params:
|
||||
cursor.execute(sql,params)
|
||||
else:
|
||||
cursor.execute(sql)
|
||||
except Exception,e:
|
||||
print e
|
||||
|
||||
def get_app_model_info_from_request(request):
|
||||
"""
|
||||
|
||||
"""
|
||||
if request and isinstance(request,HttpRequest):
|
||||
import re
|
||||
pattern = re.compile(r"/(admin)/(\w+)/(\w+)/(\d+)")
|
||||
match = pattern.match(request.path)
|
||||
|
||||
if match and match.group():
|
||||
app = match.group(2)
|
||||
model = match.group(3)
|
||||
oid = match.group(4)
|
||||
ct = ContentType.objects.get(app_label=app,model=model)
|
||||
obj = ct.get_object_for_this_type(id=oid)
|
||||
return {'app':app,'model':model,'id':oid,'obj':obj}
|
||||
return None
|
||||
|
||||
|
||||
class MineBOManager(models.Manager):
|
||||
"""
|
||||
get the objects created by current login user
|
||||
"""
|
||||
def get_query_set(self):
|
||||
return super(MineBOManager,self).get_query_set().filter(creator=cuser.getuser())
|
||||
|
||||
|
||||
class BOManager(models.Manager):
|
||||
"""
|
||||
"""
|
||||
def get_query_set(self):
|
||||
return super(BOManager,self).get_query_set()
|
||||
|
||||
|
||||
class BO(models.Model):
|
||||
"""
|
||||
All business object derive from this class
|
||||
"""
|
||||
begin = models.DateField(_('begin date'),blank=True,null=True)
|
||||
end = models.DateField(_('end date'),blank=True,null=True)
|
||||
creator = models.CharField(_("creator"),blank=True,null=True,max_length=const.DB_CHAR_NAME_20)
|
||||
modifier = models.CharField(_("modifier"),blank=True,null=True,max_length=const.DB_CHAR_NAME_20)
|
||||
creation = models.DateTimeField(_('creation'),auto_now_add=True,blank=True,null=True)
|
||||
modification = models.DateTimeField(_('modification'),auto_now=True,blank=True,null=True)
|
||||
# mine = MineBOManager()
|
||||
objects = models.Manager()
|
||||
|
||||
def __unicode__(self):
|
||||
display = getattr(self,'name',None) or getattr(self,'title',None) or getattr(self,'description',None)
|
||||
if not display:
|
||||
display = ' '
|
||||
return u'%s' % display
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class BOAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
All business object admin derive from this class
|
||||
"""
|
||||
CODE_NUMBER_WIDTH = 4
|
||||
CODE_PREFIX = '9'
|
||||
extra_buttons = []
|
||||
|
||||
exclude = ['creator','modifier','creation','modification','begin','end']
|
||||
list_per_page = 18
|
||||
actions = ['export_selected_data']
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
"""
|
||||
|
||||
:param request:
|
||||
:param object_id:
|
||||
:param form_url:
|
||||
:param extra_context:
|
||||
:return:
|
||||
"""
|
||||
app_info = get_app_model_info_from_request(request)
|
||||
workflow_modal = None
|
||||
workflow_instance = None
|
||||
has_workflow_modal = False
|
||||
has_workflow_instance = False
|
||||
show_workflow_line = False
|
||||
show_submit_button = False
|
||||
can_restart = False
|
||||
can_edit = False
|
||||
# print app_info
|
||||
if app_info:
|
||||
try:
|
||||
modal = ContentType.objects.get(app_label='workflow',model='modal')
|
||||
workflow_modal = modal.get_object_for_this_type(app_name=app_info['app'],model_name=app_info['model'])
|
||||
has_workflow_modal = True
|
||||
# print workflow_modal.code
|
||||
instance = ContentType.objects.get(app_label='workflow',model='instance')
|
||||
workflow_instance = instance.get_object_for_this_type(modal=workflow_modal,object_id=app_info['id'])
|
||||
has_workflow_instance = True
|
||||
todo = ContentType.objects.get(app_label='workflow',model='todolist')
|
||||
todo_list = todo.model_class().objects.filter(inst=workflow_instance,status=0,user=request.user)
|
||||
x = todo_list.all()
|
||||
|
||||
if x and len(x)>0:
|
||||
can_edit = x[0].node.can_edit
|
||||
if todo_list.count() > 0:
|
||||
# print 'we fount it'
|
||||
unread = todo_list.filter(is_read=0)
|
||||
show_workflow_line = True
|
||||
if unread.count() > 0:
|
||||
unread.update(is_read=1,read_time=datetime.datetime.now())
|
||||
if workflow_instance.status == 3 and request.user == workflow_instance.starter:
|
||||
can_restart = True
|
||||
show_workflow_line = True
|
||||
|
||||
except Exception,e:
|
||||
print Exception,e
|
||||
|
||||
if workflow_modal and not workflow_instance:
|
||||
show_submit_button = True
|
||||
extra_context = extra_context or {}
|
||||
ctx = dict(
|
||||
has_workflow_instance = has_workflow_instance,
|
||||
has_workflow_modal = has_workflow_modal,
|
||||
workflow_modal = workflow_modal,
|
||||
workflow_instance = workflow_instance,
|
||||
show_workflow_line = show_workflow_line,
|
||||
can_restart = can_restart,
|
||||
can_edit = can_edit,
|
||||
show_submit_button = show_submit_button,
|
||||
)
|
||||
if len(self.extra_buttons) > 0:
|
||||
buttons = dict(
|
||||
extra_buttons = self.extra_buttons
|
||||
)
|
||||
ctx.update(buttons)
|
||||
extra_context.update(ctx)
|
||||
# print extra_context
|
||||
return super(BOAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
def history_view(self, request, object_id, extra_context=None):
|
||||
"""
|
||||
|
||||
:param request:
|
||||
:param object_id:
|
||||
:param extra_context:
|
||||
:return:
|
||||
"""
|
||||
app_info = get_app_model_info_from_request(request)
|
||||
# print app_info
|
||||
if app_info:
|
||||
try:
|
||||
modal = ContentType.objects.get(app_label='workflow',model='modal')
|
||||
workflow_modal = modal.get_object_for_this_type(app_name=app_info['app'],model_name=app_info['model'])
|
||||
has_workflow_modal = True
|
||||
instance = ContentType.objects.get(app_label='workflow',model='instance')
|
||||
workflow_instance = instance.get_object_for_this_type(modal=workflow_modal,object_id=app_info['id'])
|
||||
has_workflow_instance = True
|
||||
history = ContentType.objects.get(app_label='workflow',model='history')
|
||||
history_list = history.model_class().objects.filter(inst=workflow_instance)
|
||||
has_history = True
|
||||
todo = ContentType.objects.get(app_label='workflow',model='todolist')
|
||||
todo_list = todo.model_class().objects.filter(inst=workflow_instance,status=0).exclude(node=None)
|
||||
|
||||
extra_context = extra_context or {}
|
||||
ctx = dict(
|
||||
has_workflow_instance = has_workflow_instance,
|
||||
has_workflow_modal = has_workflow_modal,
|
||||
workflow_modal = workflow_modal,
|
||||
workflow_instance = workflow_instance,
|
||||
history_list = history_list,
|
||||
has_history = has_history,
|
||||
todo_list = todo_list,
|
||||
)
|
||||
# print history_list
|
||||
extra_context.update(ctx)
|
||||
except Exception,e:
|
||||
print Exception,e
|
||||
pass
|
||||
return super(BOAdmin,self).history_view(request,object_id,extra_context)
|
||||
|
||||
def get_changeform_initial_data(self, request):
|
||||
import datetime
|
||||
return {'begin':datetime.date.today,'end':datetime.date(9999,12,31)}
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
|
||||
if change:
|
||||
setattr(obj,'modifier',request.user.username)
|
||||
else:
|
||||
setattr(obj,'creator',request.user.username)
|
||||
setattr(obj,'begin',datetime.date.today())
|
||||
setattr(obj,'end',datetime.date(9999,12,31))
|
||||
try:
|
||||
setattr(obj,'user',request.user)
|
||||
except Exception,e:
|
||||
pass
|
||||
|
||||
super(BOAdmin,self).save_model(request,obj,form,change)
|
||||
# print '=========it is here========='
|
||||
try:
|
||||
code = getattr(obj,'code')
|
||||
# print code
|
||||
if code is None or len(code) == 0:
|
||||
fmt = '%s%0'+str(self.CODE_NUMBER_WIDTH)+'d'
|
||||
code = fmt % (self.CODE_PREFIX,obj.id)
|
||||
table = obj._meta.db_table
|
||||
sql = 'update %s set code = \'%s\' where id=%s' % (table,code,obj.id)
|
||||
print sql
|
||||
update(sql)
|
||||
except Exception,e:
|
||||
print e
|
||||
|
||||
# def response_change(self, request, obj):
|
||||
# return HttpResponseRedirect('')
|
||||
|
||||
def export_selected_data(self,request,queryset):
|
||||
ops = self.model._meta
|
||||
workbook = xlwt.Workbook(encoding='utf-8')
|
||||
dd = datetime.date.today().strftime('%Y%m%d')
|
||||
file_name = force_text(ops.verbose_name+dd)
|
||||
sheet = workbook.add_sheet(force_text(ops.verbose_name))
|
||||
obj_fields = getattr(self,'export_fields',None) or self.list_display or self.fields
|
||||
|
||||
head_col_index = 0
|
||||
for field in obj_fields:
|
||||
col_name = field
|
||||
try:
|
||||
f = ops.get_field(field)
|
||||
col_name = f.verbose_name
|
||||
except Exception,e:
|
||||
f = getattr(self.model,field)
|
||||
if hasattr(f,'short_description'):
|
||||
col_name = f.short_description
|
||||
sheet.write(0,head_col_index,force_text(col_name))
|
||||
head_col_index+=1
|
||||
row_index = 1
|
||||
for obj in queryset:
|
||||
col_index = 0
|
||||
for field in obj_fields:
|
||||
f = field
|
||||
try:
|
||||
f = ops.get_field(field)
|
||||
except Exception,e:
|
||||
pass
|
||||
v = getattr(obj,field,'')
|
||||
if hasattr(v,'__call__') or callable(v):
|
||||
v = v()
|
||||
elif type(f) == fields.DateField:
|
||||
v = v.strftime('%Y-%m-%d')
|
||||
elif type(f) == fields.DateTimeField:
|
||||
v = v.strftime('%Y-%m-%d %H:%M')
|
||||
elif type(f) == fields.CharField and f.choices:
|
||||
fc = 'get_'+field+'_display'
|
||||
v = getattr(obj,fc)()
|
||||
elif type(f) == related.ForeignKey:
|
||||
v = str(v)
|
||||
sheet.write(row_index,col_index,v)
|
||||
col_index += 1
|
||||
row_index += 1
|
||||
response = HttpResponse(content_type='application/vnd.ms-excel')
|
||||
agent = request.META.get('HTTP_USER_AGENT')
|
||||
nn = smart_str(file_name)
|
||||
if agent and re.search('MSIE',agent):
|
||||
nn = urlquote(file_name)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s.xls'%nn
|
||||
workbook.save(response)
|
||||
return response
|
||||
#self.message_user(request,'SUCCESS')
|
||||
export_selected_data.short_description = _("export selected %(verbose_name_plural)s")
|
||||
|
||||
class Meta:
|
||||
ordering = ['-creation']
|
||||
|
||||
class Media:
|
||||
css = {'all':('css/maximus.css',)}
|
||||
js = ('js/maximus.js',)
|
||||
@@ -0,0 +1,30 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- "6010:8000"
|
||||
depends_on:
|
||||
- db
|
||||
environment:
|
||||
- DATABASE_HOST=db
|
||||
- DATABASE_NAME=mis
|
||||
- DATABASE_USER=root
|
||||
- DATABASE_PASSWORD=root
|
||||
|
||||
db:
|
||||
image: mysql:5.7
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./Install/SQL:/docker-entrypoint-initdb.d
|
||||
environment:
|
||||
- MYSQL_DATABASE=mis
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- MYSQL_PASSWORD=root
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
@@ -0,0 +1,32 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- "6010:8000"
|
||||
depends_on:
|
||||
- db
|
||||
environment:
|
||||
- DATABASE_HOST=db
|
||||
- DATABASE_NAME=mis
|
||||
- DATABASE_USER=root
|
||||
- DATABASE_PASSWORD=root
|
||||
|
||||
db:
|
||||
image: mysql:5.7
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./Install/SQL:/docker-entrypoint-initdb.d
|
||||
environment:
|
||||
- MYSQL_DATABASE=mis
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- MYSQL_PASSWORD=root
|
||||
ports:
|
||||
- "6011:3306"
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
@@ -0,0 +1 @@
|
||||
default_app_config = 'hr.apps.MyAppConfig'
|
||||
@@ -0,0 +1,47 @@
|
||||
# coding = utf-8
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import generic
|
||||
from common import const
|
||||
from hr.models import Entry,SalaryItem,EmployeeSalaryItem
|
||||
|
||||
|
||||
class SalaryItemAdmin(admin.ModelAdmin):
|
||||
list_display = ['code','classification','name','plus_or_minus','required']
|
||||
list_display_links = ['code','name']
|
||||
list_per_page = 20
|
||||
|
||||
|
||||
class EmployeeSalaryItemInline(admin.TabularInline):
|
||||
model = EmployeeSalaryItem
|
||||
exclude = ['employee']
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||
if db_field.name == 'salary_item':
|
||||
kwargs['queryset'] = SalaryItem.objects.filter(required=1)
|
||||
|
||||
return super(EmployeeSalaryItemInline,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 3
|
||||
|
||||
|
||||
class EntryAdmin(generic.BOAdmin):
|
||||
list_display = ['code','name','gender','position','rank','probation_months','probation_end']
|
||||
inlines = [EmployeeSalaryItemInline]
|
||||
fieldsets = [
|
||||
(None,{'fields':[('code','position',),('name','pinyin',),('address','zipcode',),('idcard','phone',),('memo',),('profile',)]}),
|
||||
(_('org distribute'),{'fields':[('guider',),('rank','ygxs',),('category','probation_months',),('probation_begin','probation_end',)],'classes': ['collapse']})
|
||||
]
|
||||
raw_id_fields = ['position','guider']
|
||||
def get_changeform_initial_data(self, request):
|
||||
import datetime
|
||||
end = datetime.date.today()+datetime.timedelta(90)
|
||||
return {'probation_end':end}
|
||||
|
||||
|
||||
admin.site.register(SalaryItem,SalaryItemAdmin)
|
||||
admin.site.register(Entry,EntryAdmin)
|
||||
@@ -0,0 +1,11 @@
|
||||
# created at 15-5-23
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class MyAppConfig(AppConfig):
|
||||
name = 'hr'
|
||||
verbose_name = _("human resource")
|
||||
@@ -0,0 +1,125 @@
|
||||
# coding=utf-8
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import generic
|
||||
from common import const
|
||||
from basedata.models import Position,Employee
|
||||
from organ.models import OrgUnit
|
||||
import datetime
|
||||
|
||||
|
||||
class SalaryItemHandler:
|
||||
code = None
|
||||
|
||||
def __init__(self,employee):
|
||||
self.employee = employee
|
||||
|
||||
def value(self):
|
||||
return 0
|
||||
|
||||
|
||||
class SalaryItem(models.Model):
|
||||
"""
|
||||
工资项
|
||||
"""
|
||||
formulas = {}
|
||||
|
||||
@classmethod
|
||||
def add_formula(cls, code, handler):
|
||||
SalaryItem.formulas[code] = handler
|
||||
|
||||
@classmethod
|
||||
def get_formula(cls):
|
||||
return SalaryItem.formulas.get(cls.code,None)
|
||||
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_10,null=True)
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||
classification = models.CharField(_("classification"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S048'),default='10')
|
||||
plus_or_minus = models.CharField(_("plus or minus"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S049'),default='+')
|
||||
required = models.BooleanField(_("is required"),default=0)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s %s" % (self.code,self.name)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('salary item')
|
||||
verbose_name_plural = _('salary items')
|
||||
ordering = ('code',)
|
||||
|
||||
|
||||
class Entry(generic.BO):
|
||||
"""
|
||||
人员入职
|
||||
"""
|
||||
code = models.CharField(_("employee number"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
name = models.CharField(_("employee name"),max_length=const.DB_CHAR_NAME_120)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
birthday = models.DateField(_("birthday"),blank=True,null=True)
|
||||
gender = models.CharField(_("gender"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('gender'),default='1')
|
||||
idcard = models.CharField(_("id card"),max_length=const.DB_CHAR_NAME_20)
|
||||
address = models.CharField(_("mail address"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
zipcode = models.CharField(_("zipcode"),max_length=const.DB_CHAR_CODE_8)
|
||||
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
|
||||
guider = models.ForeignKey(Employee,verbose_name=_("guider"))
|
||||
position = models.ForeignKey(Position,verbose_name = _('designate position'))
|
||||
rank = models.CharField(_("employee rank"),max_length=const.DB_CHAR_CODE_2,default='00',choices=const.get_value_list('S017'))
|
||||
ygxs = models.CharField(_("employ ygxs"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S019'),default='2')
|
||||
category = models.CharField(_("employ category"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S018'),default='21')
|
||||
|
||||
probation_months = models.CharField(_("probation months"),max_length=2,default='3',choices=const.get_value_list('S047'))
|
||||
probation_begin = models.DateField(_("probation begin"),default=datetime.date.today)
|
||||
probation_end = models.DateField(_("probation end"),blank=True,null=True)
|
||||
|
||||
memo = models.TextField(_("memo"),blank=True,null=True)
|
||||
profile = models.FileField(_("profile"),blank=True,null=True,upload_to='hr profile')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("employee entry")
|
||||
verbose_name_plural = _("employee entries")
|
||||
permissions = (
|
||||
('modify_salary_item',_("modify salary item")),
|
||||
)
|
||||
|
||||
|
||||
class EmployeeSalaryItem(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
entry = models.ForeignKey(Entry,verbose_name=_("employee entry"))
|
||||
employee = models.ForeignKey(Employee,verbose_name=_("employee"),blank=True,null=True)
|
||||
salary_item = models.ForeignKey(SalaryItem,verbose_name=_("salary item"))
|
||||
calculate_way = models.CharField(_("calculate way"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S050'),default='10')
|
||||
fixed_value = models.DecimalField(_("fixed value"),blank=True,null=True,max_digits=10,decimal_places=2)
|
||||
base_value = models.DecimalField(_("base value"),blank=True,null=True,max_digits=10,decimal_places=2)
|
||||
org_percent = models.DecimalField(_("org percent"),blank=True,null=True,max_digits=4,decimal_places=2)
|
||||
employee_percent = models.DecimalField(_("employee percent"),blank=True,null=True,max_digits=4,decimal_places=2)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("salary item")
|
||||
verbose_name_plural = _("salary item")
|
||||
unique_together = (('entry', 'salary_item'),)
|
||||
|
||||
|
||||
class Transfer(generic.BO):
|
||||
"""
|
||||
人员调动
|
||||
"""
|
||||
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("employee transfer")
|
||||
verbose_name_plural = _("employee transfers")
|
||||
|
||||
|
||||
class Departure(generic.BO):
|
||||
"""
|
||||
人员离职
|
||||
"""
|
||||
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("employee departure")
|
||||
verbose_name_plural = _("employee departures")
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
|
After Width: | Height: | Size: 914 KiB |
|
After Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 123 KiB |
@@ -0,0 +1 @@
|
||||
default_app_config = "invent.apps.MyAppConfig"
|
||||
@@ -0,0 +1,248 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import generic
|
||||
from basedata.models import Material
|
||||
from invent.models import StockIn,StockOut,InitialInventory,InItem,OutItem,InitItem,Inventory,InItemForm,InOutDetail,\
|
||||
WareReturn,ReturnItem,WareAdjust,AdjustItem
|
||||
|
||||
|
||||
class InItemInline(admin.TabularInline):
|
||||
model = InItem
|
||||
form = InItemForm
|
||||
fields = ('material', 'measure', 'cnt', 'price')
|
||||
raw_id_fields = ['material']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj and obj.status == 1:
|
||||
return ['material', 'measure', 'cnt', 'price']
|
||||
else:
|
||||
return []
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||
if db_field.name == 'material':
|
||||
kwargs['queryset'] = Material.objects.filter(is_virtual=0)
|
||||
return super(InItemInline,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||
|
||||
|
||||
class OutItemInline(admin.TabularInline):
|
||||
model = OutItem
|
||||
fields = ('inventory', 'measure', 'cnt', 'price','warehouse',)
|
||||
raw_id_fields = ['inventory']
|
||||
readonly_fields = ['measure', 'price', 'warehouse']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 3
|
||||
|
||||
|
||||
class InitItemInline(admin.TabularInline):
|
||||
model = InitItem
|
||||
fields = ('material', 'measure', 'cnt', 'warehouse', 'price',)
|
||||
raw_id_fields = ['material']
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj and obj.execute_time:
|
||||
return ['material', 'measure', 'cnt', 'warehouse', 'price']
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 3
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||
if db_field.name == 'material':
|
||||
kwargs['queryset'] = Material.objects.filter(is_virtual=0)
|
||||
return super(InitItemInline,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||
|
||||
|
||||
class StockInAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'RK'
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
list_display = ['code','title','money_of_amount','status','entry_time']
|
||||
inlines = [InItemInline]
|
||||
raw_id_fields = ['po']
|
||||
fields = (
|
||||
('code',),('title',),('po',),('warehouse',),('batch',),('status','amount',)
|
||||
)
|
||||
date_hierarchy = 'begin'
|
||||
extra_buttons = [{'href':'cin','title':_('Action Stock In')}]
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
import decimal
|
||||
super(StockInAdmin,self).save_model(request,obj,form,change)
|
||||
if obj and obj.po:
|
||||
po_items = obj.po.poitem_set.filter(left_cnt__gt=0).all()
|
||||
for item in po_items:
|
||||
try:
|
||||
InItem.objects.get(po_item=item,master=obj)
|
||||
continue
|
||||
except Exception,e:
|
||||
pp = item.discount_price or item.price
|
||||
if decimal.Decimal(item.tax) > decimal.Decimal(0):
|
||||
pp = pp /(decimal.Decimal(1)+decimal.Decimal(item.tax))
|
||||
InItem.objects.create(warehouse=obj.warehouse,material=item.material,measure=item.measure,prop='+',
|
||||
po_item=item,master=obj,cnt=item.left_cnt,price=pp,batch=obj.batch,source=obj.code)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
print obj
|
||||
if obj and obj.status == 9:
|
||||
return ['code','title','po','warehouse','batch','status']
|
||||
else:
|
||||
return ['status','amount']
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
if object_id:
|
||||
obj = StockIn.objects.get(id=object_id)
|
||||
if obj and obj.execute_time:
|
||||
extra_context.update(dict(readonly=True))
|
||||
return super(StockInAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
|
||||
class StockOutAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'CK'
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
list_display = ['code','title','project','status','out_time','out_amount']
|
||||
list_display_links = ['code','title']
|
||||
date_hierarchy = 'begin'
|
||||
inlines = [OutItemInline]
|
||||
raw_id_fields = ['project','wo','user']
|
||||
fields = (
|
||||
('code', 'status',),('project', ),('wo','user'),
|
||||
('title','amount',),('description',),
|
||||
)
|
||||
readonly_fields = ['status']
|
||||
extra_buttons = [{'href':'out','title':_('Action Stock Out')}]
|
||||
search_fields = ['code','title','user__username']
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
if object_id:
|
||||
obj = StockOut.objects.get(id=object_id)
|
||||
if obj and obj.execute_time:
|
||||
extra_context.update(dict(readonly=True))
|
||||
return super(StockOutAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj and obj.user is None:
|
||||
obj.user = request.user
|
||||
super(StockOutAdmin,self).save_model(request,obj,form,change)
|
||||
|
||||
|
||||
class InitialInventoryAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'QC'
|
||||
CODE_NUMBER_WIDTH = 3
|
||||
list_display = ['code','title','status']
|
||||
inlines = [InitItemInline]
|
||||
fields = ('code','title','org','status','amount','attach')
|
||||
readonly_fields = ['status','amount']
|
||||
extra_buttons = [{'href':'cin','title':_('Action Stock In')}]
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
if object_id:
|
||||
obj = InitialInventory.objects.get(id=object_id)
|
||||
if obj and obj.execute_time:
|
||||
extra_context.update(dict(readonly=True))
|
||||
return super(InitialInventoryAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
|
||||
class InventoryAdmin(generic.BOAdmin):
|
||||
list_display = ['material','measure','warehouse','cnt','price']
|
||||
search_fields = ['material__name','material__code']
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context.update(dict(readonly=True))
|
||||
if object_id:
|
||||
inventory = Inventory.objects.get(id=object_id)
|
||||
material = inventory.material
|
||||
warehouse = inventory.warehouse
|
||||
detail = InOutDetail.objects.filter(material=material,warehouse=warehouse)
|
||||
extra_context.update(dict(detail=detail))
|
||||
|
||||
return super(InventoryAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
|
||||
class ReturnItemInline(admin.TabularInline):
|
||||
model = ReturnItem
|
||||
fields = ['material','measure','warehouse','out_cnt','cnt']
|
||||
readonly_fields = ['material','measure','warehouse','out_cnt']
|
||||
extra = 0
|
||||
|
||||
|
||||
class WareReturnAdmin(generic.BOAdmin):
|
||||
"""
|
||||
|
||||
"""
|
||||
CODE_PREFIX = 'FK'
|
||||
CODE_NUMBER_WIDTH = 4
|
||||
list_display = ['code','title','out']
|
||||
fields = (
|
||||
('code',),('title',),('out',),('amount',),('status',)
|
||||
)
|
||||
readonly_fields = ['status']
|
||||
raw_id_fields = ['out']
|
||||
inlines = [ReturnItemInline]
|
||||
extra_buttons = [{'href':'cin','title':_('Action Ware Return')}]
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
if object_id :
|
||||
obj = WareReturn.objects.get(id=object_id)
|
||||
if obj.status == '9':
|
||||
extra_context = extra_context or {}
|
||||
extra_context.update(dict(readonly=True))
|
||||
return super(WareReturnAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
|
||||
class AdjustItemInline(admin.TabularInline):
|
||||
model = AdjustItem
|
||||
fields = ['inventory','measure','warehouse','prop','cnt']
|
||||
readonly_fields = ['measure','warehouse']
|
||||
raw_id_fields = ['inventory']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
class WareAdjustAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'TZ'
|
||||
CODE_NUMBER_WIDTH = 3
|
||||
list_display = ['code','title','status']
|
||||
fields = (
|
||||
('code',),('title',),('description',),('status',)
|
||||
)
|
||||
readonly_fields = ['status']
|
||||
inlines = [AdjustItemInline]
|
||||
extra_buttons = [{'href':'adjust','title':_('Action Ware Adjust')}]
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
if object_id:
|
||||
obj = WareAdjust.objects.get(id=object_id)
|
||||
if obj and obj.status == '9':
|
||||
extra_context = extra_context or {}
|
||||
extra_context.update(dict(readonly=True))
|
||||
return super(WareAdjustAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
|
||||
admin.site.register(StockIn,StockInAdmin)
|
||||
admin.site.register(StockOut,StockOutAdmin)
|
||||
admin.site.register(InitialInventory,InitialInventoryAdmin)
|
||||
admin.site.register(Inventory,InventoryAdmin)
|
||||
admin.site.register(WareReturn,WareReturnAdmin)
|
||||
admin.site.register(WareAdjust,WareAdjustAdmin)
|
||||
@@ -0,0 +1,11 @@
|
||||
# created at 15-5-23
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class MyAppConfig(AppConfig):
|
||||
name = 'invent'
|
||||
verbose_name = _("inventory manage")
|
||||
@@ -0,0 +1,493 @@
|
||||
# coding=utf-8
|
||||
import csv
|
||||
import os
|
||||
import datetime
|
||||
import decimal
|
||||
from django.db import transaction
|
||||
from django.db import models
|
||||
from django import forms
|
||||
from mis import settings
|
||||
from django.utils.text import force_text
|
||||
from django.db import transaction
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import generic
|
||||
from common import const
|
||||
from basedata.models import Material,Warehouse,Measure,Organization,Project
|
||||
from selfhelp.models import WorkOrder
|
||||
from purchase.models import PurchaseOrder,POItem
|
||||
|
||||
|
||||
class Inventory(generic.BO):
|
||||
"""
|
||||
库存信息
|
||||
"""
|
||||
index_weight = 1
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
warehouse = models.ForeignKey(Warehouse,verbose_name=_("warehouse"))
|
||||
material = models.ForeignKey(Material,verbose_name=_("material"))
|
||||
measure = models.ForeignKey(Measure,verbose_name=_("measure"))
|
||||
cnt = models.DecimalField(_("count"),max_digits=14,decimal_places=4)
|
||||
batch = models.CharField(_("batch"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
price = models.DecimalField(_("price"),max_digits=14,decimal_places=4)
|
||||
|
||||
def __unicode__(self):
|
||||
des = self.material.spec or ''
|
||||
return u'%s %s %s' % (self.material.code,self.material.name,des)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Inventory")
|
||||
verbose_name_plural = _("Inventory")
|
||||
ordering = ['material']
|
||||
|
||||
|
||||
class InitialInventory(generic.BO):
|
||||
"""
|
||||
期初库存
|
||||
"""
|
||||
STATUS = (
|
||||
('0', _("NEW")),
|
||||
('9', _("EXECUTED"))
|
||||
)
|
||||
index_weight = 9
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||
attach = models.FileField(_('attach'),blank=True,null=True,upload_to='inventory',help_text=u'参考FD0002模板文档')
|
||||
amount = models.DecimalField(_('money of amount'),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
|
||||
super(InitialInventory,self).save(force_insert,force_update,using,update_fields)
|
||||
count = InitItem.objects.filter(master=self).count()
|
||||
if self.attach and count == 0:
|
||||
path = os.path.join(settings.MEDIA_ROOT,self.attach.name)
|
||||
reader = csv.reader(open(path,'r'))
|
||||
index = 0
|
||||
with transaction.atomic():
|
||||
for code,name,description,unit_code,unit_name,warehouse_code,warehouse_name,price,cnt in reader:
|
||||
index+=1
|
||||
if index == 1:
|
||||
continue
|
||||
|
||||
material = None
|
||||
measure = None
|
||||
house = None
|
||||
|
||||
try:
|
||||
measure = Measure.objects.get(code=unit_code)
|
||||
except Exception,e:
|
||||
measure = Measure.objects.create(code=unit_code,name=force_text(unit_name.decode('gbk')))
|
||||
|
||||
try:
|
||||
house = Warehouse.objects.get(code=warehouse_code)
|
||||
except Exception,e:
|
||||
house = Warehouse.objects.create(code=warehouse_code,name=force_text(warehouse_name.decode('gbk')))
|
||||
|
||||
try:
|
||||
material = Material.objects.get(code=code)
|
||||
except Exception,e:
|
||||
material = Material(code=code,name=force_text(name.decode('gbk')),spec=force_text(description.decode('gbk')),warehouse=house)
|
||||
material.stock_price = price
|
||||
material.save()
|
||||
InitItem.objects.create(master=self,material=material,price=price,cnt=cnt,warehouse=house,measure=measure,source=self.code)
|
||||
|
||||
def init_entry(self,request=None):
|
||||
"""
|
||||
执行期初入库
|
||||
"""
|
||||
count = InitItem.objects.filter(master=self).count()
|
||||
if count > 0:
|
||||
with transaction.atomic():
|
||||
total_amount = 0
|
||||
for item in InitItem.objects.filter(master=self).all():
|
||||
Inventory.objects.create(material=item.material,price=item.price,cnt=item.cnt,warehouse=item.warehouse,measure=item.measure)
|
||||
item.status = 1
|
||||
item.event_time = datetime.datetime.now()
|
||||
item.source = self.code
|
||||
item.save()
|
||||
item.material.stock_price = item.price
|
||||
if item.material.measure.count() == 0:
|
||||
item.material.measure.add(item.measure)
|
||||
item.material.save()
|
||||
total_amount += item.price*item.cnt
|
||||
self.amount = total_amount
|
||||
self.status = '9'
|
||||
self.execute_time = datetime.datetime.now()
|
||||
self.save()
|
||||
else:
|
||||
raise Exception(_("none material was found"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Initial Inventory")
|
||||
verbose_name_plural = _("Initial Inventory")
|
||||
|
||||
|
||||
class StockIn(generic.BO):
|
||||
"""
|
||||
入库单
|
||||
"""
|
||||
STATUS = (
|
||||
('0', _("NEW")),
|
||||
('1', _("QUALITY TESTING")),
|
||||
('9', _("EXECUTED"))
|
||||
)
|
||||
index_weight = 3
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
warehouse = models.ForeignKey(Warehouse,verbose_name=_("warehouse"))
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||
po = models.ForeignKey(PurchaseOrder,verbose_name=_("purchase order"),null=True,limit_choices_to={"entry_status":"0"},blank=True)
|
||||
amount = models.DecimalField(_("stock in money of amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
batch = models.CharField(_("batch"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
|
||||
def money_of_amount(self):
|
||||
if self.amount:
|
||||
return self.amount
|
||||
else:
|
||||
return 0.00
|
||||
|
||||
def entry_time(self):
|
||||
if self.execute_time:
|
||||
return self.execute_time
|
||||
else:
|
||||
return ''
|
||||
|
||||
money_of_amount.short_description = _("stock in money of amount")
|
||||
entry_time.short_description = _("entry time")
|
||||
|
||||
def action_entry(self,request):
|
||||
"""
|
||||
执行入库操作
|
||||
"""
|
||||
if self.initem_set.count() > 0:
|
||||
with transaction.atomic():
|
||||
total_amount = decimal.Decimal(0)
|
||||
for item in self.initem_set.filter(status=0).all():
|
||||
try:
|
||||
inventory = Inventory.objects.get(warehouse=self.warehouse,material=item.material,measure=item.measure)
|
||||
if inventory.price != item.price:
|
||||
at = decimal.Decimal(inventory.price*inventory.cnt+item.price*item.cnt)
|
||||
ac = decimal.Decimal(inventory.cnt+item.cnt)
|
||||
average = at/ac
|
||||
inventory.price = average
|
||||
item.material.stock_price = average
|
||||
item.material.save()
|
||||
inventory.cnt += item.cnt
|
||||
inventory.save()
|
||||
total_amount += item.price*item.cnt
|
||||
except Exception,e:
|
||||
Inventory.objects.create(warehouse=self.warehouse,material=item.material,measure=item.measure,
|
||||
cnt=item.cnt,price=item.price,org=self.org)
|
||||
item.material.stock_price = item.price
|
||||
item.material.save()
|
||||
|
||||
item.status=1
|
||||
item.event_time = datetime.datetime.now()
|
||||
item.source = self.code
|
||||
item.save()
|
||||
total_amount += item.price*item.cnt
|
||||
# saving the purchase item
|
||||
item.po_item.is_in_stock = 1
|
||||
item.po_item.in_stock_time = datetime.datetime.now()
|
||||
item.po_item.entry_cnt = item.cnt
|
||||
|
||||
item.po_item.save()
|
||||
none_zero_left = POItem.objects.filter(po=item.po_item.po,left_cnt__gt=0).count()
|
||||
if none_zero_left == 0:
|
||||
item.po_item.po.status = '99'
|
||||
item.po_item.po.entry_status = 1
|
||||
item.po_item.po.entry_time = datetime.datetime.now()
|
||||
item.po_item.po.save()
|
||||
# saving stock in item status
|
||||
item.status = 1
|
||||
item.event_time = datetime.datetime.now()
|
||||
# updating master's record
|
||||
self.status = 9
|
||||
self.execute_time = datetime.datetime.now()
|
||||
self.amount = total_amount
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("StockIn")
|
||||
verbose_name_plural = _("StockIn")
|
||||
|
||||
|
||||
class StockOut(generic.BO):
|
||||
"""
|
||||
领料单
|
||||
"""
|
||||
STATUS = (
|
||||
('0', _("NEW")),
|
||||
('1', _("IN PROGRESS")),
|
||||
('9', _("EXECUTED"))
|
||||
)
|
||||
index_weight = 2
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||
project = models.ForeignKey(Project,verbose_name=_("project"),blank=True,null=True)
|
||||
wo = models.ForeignKey(WorkOrder,verbose_name=_("work order"),blank=True,null=True)
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
amount = models.DecimalField(_("money of amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
user = models.ForeignKey(User,verbose_name=_("out user"),blank=True,null=True)
|
||||
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||
|
||||
def out_amount(self):
|
||||
return self.amount or ''
|
||||
|
||||
def out_time(self):
|
||||
return self.execute_time or ''
|
||||
|
||||
out_time.short_description = _('stock out time')
|
||||
out_amount.short_description = _('stock out amount')
|
||||
|
||||
def action_out(self,request=None):
|
||||
"""
|
||||
执行出库操作
|
||||
"""
|
||||
if self.outitem_set.count() > 0:
|
||||
with transaction.atomic():
|
||||
total = decimal.Decimal(0)
|
||||
for item in OutItem.objects.filter(master=self).all():
|
||||
if item.inventory.cnt < item.cnt:
|
||||
raise Exception('%s does not meets required' % item.material)
|
||||
if item.cnt:
|
||||
total += item.cnt * item.inventory.price
|
||||
item.inventory.cnt -= item.cnt
|
||||
item.status = 1
|
||||
item.price = item.inventory.price
|
||||
item.event_time = datetime.datetime.now()
|
||||
item.inventory.save()
|
||||
item.source=self.code
|
||||
item.save()
|
||||
if total > 0:
|
||||
self.amount = total
|
||||
self.status = '9'
|
||||
self.execute_time = datetime.datetime.now()
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("StockOut")
|
||||
verbose_name_plural = _("StockOut")
|
||||
|
||||
|
||||
class WareReturn(generic.BO):
|
||||
"""
|
||||
返库单
|
||||
"""
|
||||
STATUS = (
|
||||
('0', _("NEW")),
|
||||
('1', _("IN PROGRESS")),
|
||||
('9', _("EXECUTED"))
|
||||
)
|
||||
index_weight = 5
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||
out = models.ForeignKey(StockOut,verbose_name=_('StockOut'))
|
||||
amount = models.DecimalField(_("money of amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
user = models.ForeignKey(User,verbose_name=_("out user"),blank=True,null=True)
|
||||
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
super(WareReturn,self).save(force_insert,force_update,using,update_fields)
|
||||
item_count = ReturnItem.objects.filter(master=self).count()
|
||||
if self.out and item_count == 0:
|
||||
for out_item in OutItem.objects.filter(master=self.out):
|
||||
ReturnItem.objects.create(master=self,out_item=out_item,material=out_item.material,price=out_item.price,
|
||||
measure=out_item.measure,warehouse=out_item.warehouse,out_cnt=out_item.cnt,cnt=out_item.cnt)
|
||||
|
||||
def action_return(self,request):
|
||||
with transaction.atomic():
|
||||
total_amount = decimal.Decimal(0)
|
||||
for item in ReturnItem.objects.filter(master=self):
|
||||
if item.cnt > item.out_item.cnt or item.cnt < 0:
|
||||
raise Exception('%s cnt is invalid,out is %s,return is %s' % (item.material,item.out_cnt,item.cnt))
|
||||
item.event_time = datetime.datetime.now()
|
||||
item.source = self.code
|
||||
item.status = 1
|
||||
item.out_item.inventory.cnt += item.cnt
|
||||
item.out_item.inventory.save()
|
||||
item.save()
|
||||
total_amount += item.price * item.cnt
|
||||
self.amount = total_amount
|
||||
self.status = '9'
|
||||
self.execute_time = datetime.datetime.now()
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("ware return")
|
||||
verbose_name_plural = _("ware return")
|
||||
|
||||
|
||||
class WareAdjust(generic.BO):
|
||||
"""
|
||||
库存调整
|
||||
"""
|
||||
STATUS = (
|
||||
('0', _("NEW")),
|
||||
('1', _("IN PROGRESS")),
|
||||
('9', _("EXECUTED"))
|
||||
)
|
||||
index_weight = 4
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
user = models.ForeignKey(User,verbose_name=_("out user"),blank=True,null=True)
|
||||
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||
|
||||
def action_adjust(self,request):
|
||||
with transaction.atomic():
|
||||
for item in AdjustItem.objects.filter(master=self,status=0):
|
||||
inventory = item.inventory
|
||||
if item.prop == '+':
|
||||
inventory.cnt += item.cnt
|
||||
else:
|
||||
inventory.cnt -= item.cnt
|
||||
inventory.save()
|
||||
item.status = 1
|
||||
item.event_time = datetime.datetime.now()
|
||||
item.source = self.code
|
||||
item.save()
|
||||
self.status = '9'
|
||||
self.execute_time = datetime.datetime.now()
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("ware adjust")
|
||||
verbose_name_plural = _("ware adjust")
|
||||
|
||||
|
||||
class InOutDetail(models.Model):
|
||||
"""
|
||||
in out detail
|
||||
"""
|
||||
|
||||
PROP = (
|
||||
('+', _("PLUS")),
|
||||
('-', _("MINUS"))
|
||||
)
|
||||
|
||||
create_time = models.DateTimeField(_("create time"),auto_now_add=True)
|
||||
status = models.BooleanField(_("executed"),default=0)
|
||||
event_time = models.DateTimeField(_("event time"),blank=True,null=True)
|
||||
warehouse = models.ForeignKey(Warehouse,verbose_name=_("warehouse"),blank=True,null=True)
|
||||
material = models.ForeignKey(Material,verbose_name=_("material"),limit_choices_to={"is_virtual":"0"},blank=True,null=True)
|
||||
measure = models.ForeignKey(Measure,verbose_name=_("measure"),blank=True,null=True)
|
||||
cnt = models.DecimalField(_("count"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
batch = models.CharField(_("batch"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
price = models.DecimalField(_("price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
prop = models.CharField(_("plus or minus property"),max_length=const.DB_CHAR_CODE_2,choices=PROP,default='+')
|
||||
source = models.CharField(_("source"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
|
||||
|
||||
class InitItem(InOutDetail):
|
||||
"""
|
||||
期初入库明细
|
||||
"""
|
||||
master = models.ForeignKey(InitialInventory)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("init item")
|
||||
verbose_name_plural = _("init item")
|
||||
|
||||
|
||||
class InItem(InOutDetail):
|
||||
"""
|
||||
入库单明细
|
||||
"""
|
||||
master = models.ForeignKey(StockIn)
|
||||
po_item = models.ForeignKey(POItem,verbose_name=_("po item"),blank=True,null=True)
|
||||
|
||||
def get_new_price(self):
|
||||
if self.po_item and self.master.warehouse:
|
||||
try:
|
||||
inventory = Inventory.objects.get(warehouse=self.master.warehouse,material=self.material,measure=self.measure)
|
||||
if inventory and self.price != inventory.price:
|
||||
total_amount = self.price*self.cnt+inventory.price*inventory.cnt
|
||||
total_count = self.cnt+inventory.cnt
|
||||
return total_amount/total_count
|
||||
except Exception,e:
|
||||
pass
|
||||
return self.price or ''
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("in item")
|
||||
verbose_name_plural = _("in item")
|
||||
|
||||
|
||||
class InItemForm(forms.ModelForm):
|
||||
|
||||
new_price = forms.CharField(label=_('new price'),required=False)
|
||||
|
||||
class Meta:
|
||||
model = InItem
|
||||
fields = ('material', 'measure', 'cnt', 'price')
|
||||
|
||||
|
||||
class OutItem(InOutDetail):
|
||||
"""
|
||||
出库单明细
|
||||
"""
|
||||
master = models.ForeignKey(StockOut)
|
||||
inventory = models.ForeignKey(Inventory,blank=True,null=True,verbose_name=_("inventory material"))
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
self.prop = '-'
|
||||
if self.inventory:
|
||||
self.material = self.inventory.material
|
||||
self.measure = self.inventory.measure
|
||||
self.warehouse = self.inventory.warehouse
|
||||
|
||||
super(OutItem,self).save(force_insert,force_update,using,update_fields)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("out item")
|
||||
verbose_name_plural = _("out item")
|
||||
|
||||
|
||||
class ReturnItem(InOutDetail):
|
||||
"""
|
||||
返库单明细
|
||||
"""
|
||||
master = models.ForeignKey(WareReturn)
|
||||
out_item = models.ForeignKey(OutItem,blank=True,null=True,verbose_name=_('out item'))
|
||||
out_cnt = models.DecimalField(_("out count"),max_digits=14,decimal_places=4)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("return item")
|
||||
verbose_name_plural = _("return item")
|
||||
|
||||
|
||||
class AdjustItem(InOutDetail):
|
||||
"""
|
||||
库存调整明细
|
||||
"""
|
||||
master = models.ForeignKey(WareAdjust)
|
||||
inventory = models.ForeignKey(Inventory,blank=True,null=True,verbose_name=_("inventory material"))
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if self.inventory:
|
||||
self.material = self.inventory.material
|
||||
self.measure = self.inventory.measure
|
||||
self.warehouse = self.inventory.warehouse
|
||||
super(AdjustItem,self).save(force_insert,force_update,using,update_fields)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("adjust item")
|
||||
verbose_name_plural = _("adjust item")
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,10 @@
|
||||
from django.conf.urls import include, url,static
|
||||
import invent.views
|
||||
|
||||
urlpatterns = [
|
||||
url(r"stockin/(?P<object_id>\d+)/cin", invent.views.action_in),
|
||||
url(r"initialinventory/(?P<object_id>\d+)/cin", invent.views.action_init),
|
||||
url(r"stockout/(?P<object_id>\d+)/out", invent.views.action_out),
|
||||
url(r"warereturn/(?P<object_id>\d+)/cin", invent.views.action_return),
|
||||
url(r"wareadjust/(?P<object_id>\d+)/adjust", invent.views.action_adjust),
|
||||
]
|
||||
@@ -0,0 +1,175 @@
|
||||
# coding=utf-8
|
||||
from django.contrib.admin import site
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import connection
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.utils.encoding import force_text
|
||||
from django.template.response import TemplateResponse
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from invent.models import StockIn,StockOut,InitialInventory,WareReturn,WareAdjust
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
def action_in(request,object_id):
|
||||
"""
|
||||
入库操作
|
||||
:param request:
|
||||
:param object_id:
|
||||
:return:
|
||||
"""
|
||||
title = _("Are you sure?")
|
||||
obj = StockIn.objects.get(id=int(object_id))
|
||||
opts = obj._meta
|
||||
objects_name = force_text(opts.verbose_name)
|
||||
|
||||
if request.POST.get("post"):
|
||||
try:
|
||||
obj.action_entry(request)
|
||||
messages.success(request,_('check in successfully'))
|
||||
except Exception,e:
|
||||
messages.error(request,e)
|
||||
|
||||
return HttpResponseRedirect("/admin/invent/stockin/%s"%(object_id))
|
||||
|
||||
context = dict(
|
||||
site.each_context(request),
|
||||
title=title,
|
||||
opts=opts,
|
||||
objects_name=objects_name,
|
||||
object=obj,
|
||||
)
|
||||
request.current_app = site.name
|
||||
|
||||
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||
|
||||
|
||||
def action_out(request,object_id):
|
||||
"""
|
||||
出库操作
|
||||
:param request:
|
||||
:param object_id:
|
||||
:return:
|
||||
"""
|
||||
title = _("Are you sure?")
|
||||
obj = StockOut.objects.get(id=int(object_id))
|
||||
opts = obj._meta
|
||||
objects_name = force_text(opts.verbose_name)
|
||||
|
||||
if request.POST.get("post"):
|
||||
try:
|
||||
obj.action_out(request)
|
||||
messages.success(request,_('check out successfully'))
|
||||
except Exception,e:
|
||||
messages.error(request,e)
|
||||
return HttpResponseRedirect("/admin/invent/stockout/%s"%(object_id))
|
||||
|
||||
context = dict(
|
||||
site.each_context(request),
|
||||
title=title,
|
||||
opts=opts,
|
||||
objects_name=objects_name,
|
||||
object=obj,
|
||||
)
|
||||
request.current_app = site.name
|
||||
|
||||
return TemplateResponse(request,'admin/invent/stockout/out_confirmation.html', context)
|
||||
|
||||
|
||||
def action_init(request,object_id):
|
||||
"""
|
||||
期初入库操作
|
||||
:param request:
|
||||
:param object_id:
|
||||
:return:
|
||||
"""
|
||||
title = _("Are you sure?")
|
||||
obj = InitialInventory.objects.get(id=int(object_id))
|
||||
opts = obj._meta
|
||||
objects_name = force_text(opts.verbose_name)
|
||||
|
||||
if request.POST.get("post"):
|
||||
try:
|
||||
obj.init_entry(request)
|
||||
messages.success(request,_('check in successfully'))
|
||||
except Exception,e:
|
||||
messages.error(request,e)
|
||||
|
||||
return HttpResponseRedirect("/admin/invent/initialinventory/%s"%(object_id))
|
||||
|
||||
context = dict(
|
||||
site.each_context(request),
|
||||
title=title,
|
||||
opts=opts,
|
||||
objects_name=objects_name,
|
||||
object=obj,
|
||||
)
|
||||
request.current_app = site.name
|
||||
|
||||
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||
|
||||
|
||||
def action_return(request,object_id):
|
||||
"""
|
||||
返库操作
|
||||
:param request:
|
||||
:param object_id:
|
||||
:return:
|
||||
"""
|
||||
title = _("Are you sure?")
|
||||
obj = WareReturn.objects.get(id=int(object_id))
|
||||
opts = obj._meta
|
||||
objects_name = force_text(opts.verbose_name)
|
||||
|
||||
if request.POST.get("post"):
|
||||
try:
|
||||
obj.action_return(request)
|
||||
messages.success(request,_('check in successfully'))
|
||||
except Exception,e:
|
||||
messages.error(request,e)
|
||||
|
||||
return HttpResponseRedirect("/admin/invent/warereturn/%s"%(object_id))
|
||||
|
||||
context = dict(
|
||||
site.each_context(request),
|
||||
title=title,
|
||||
opts=opts,
|
||||
objects_name=objects_name,
|
||||
object=obj,
|
||||
)
|
||||
request.current_app = site.name
|
||||
|
||||
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||
|
||||
|
||||
def action_adjust(request,object_id):
|
||||
"""
|
||||
调整操作
|
||||
:param request:
|
||||
:param object_id:
|
||||
:return:
|
||||
"""
|
||||
title = _("Are you sure?")
|
||||
obj = WareAdjust.objects.get(id=int(object_id))
|
||||
opts = obj._meta
|
||||
objects_name = force_text(opts.verbose_name)
|
||||
|
||||
if request.POST.get("post"):
|
||||
try:
|
||||
obj.action_adjust(request)
|
||||
messages.success(request,_('check in successfully'))
|
||||
except Exception,e:
|
||||
messages.error(request,e)
|
||||
|
||||
return HttpResponseRedirect("/admin/invent/wareadjust/%s"%(object_id))
|
||||
|
||||
context = dict(
|
||||
site.each_context(request),
|
||||
title=title,
|
||||
opts=opts,
|
||||
objects_name=objects_name,
|
||||
object=obj,
|
||||
)
|
||||
request.current_app = site.name
|
||||
|
||||
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mis.settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
@@ -0,0 +1 @@
|
||||
__author__ = 'Administrator'
|
||||
@@ -0,0 +1,176 @@
|
||||
__author__ = 'zhugl'
|
||||
# created at 15-4-21
|
||||
#python import
|
||||
from threading import local
|
||||
from django.contrib import admin
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib.admin import ModelAdmin, actions
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse
|
||||
from django.db.models.base import ModelBase
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.template.engine import Engine
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import six
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
_thread_local = local()
|
||||
|
||||
|
||||
def getuser():
|
||||
return getattr(_thread_local,'user',None)
|
||||
|
||||
|
||||
class RequestUser(object):
|
||||
|
||||
def process_request(self,request):
|
||||
django_user = getattr(request,'user',None)
|
||||
|
||||
if django_user is not None:
|
||||
_thread_local.user = django_user
|
||||
|
||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||
app_weight = {'selfhelp':1,'purchase':3,'sale':2,'invent':4,'organ':5,'basedata':6,'syscfg':7,'workflow':8}
|
||||
if view_func.__name__ == 'index':
|
||||
app_dict = {}
|
||||
for model, model_admin in admin.site._registry.items():
|
||||
app_label = model._meta.app_label
|
||||
has_module_perms = model_admin.has_module_permission(request)
|
||||
|
||||
if has_module_perms:
|
||||
perms = model_admin.get_model_perms(request)
|
||||
if True in perms.values():
|
||||
info = (app_label, model._meta.model_name)
|
||||
model_dict = {
|
||||
'name': capfirst(model._meta.verbose_name_plural),
|
||||
'object_name': model._meta.object_name,
|
||||
'perms': perms,
|
||||
'weight':getattr(model,'index_weight',99)
|
||||
}
|
||||
if perms.get('change', False):
|
||||
try:
|
||||
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=admin.site.name)
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
if perms.get('add', False):
|
||||
try:
|
||||
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=admin.site.name)
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
if app_label in app_dict:
|
||||
app_dict[app_label]['models'].append(model_dict)
|
||||
else:
|
||||
app_dict[app_label] = {
|
||||
'name': apps.get_app_config(app_label).verbose_name,
|
||||
'app_label': app_label,
|
||||
'app_url': reverse(
|
||||
'admin:app_list',
|
||||
kwargs={'app_label': app_label},
|
||||
current_app=admin.site.name,
|
||||
),
|
||||
'has_module_perms': has_module_perms,
|
||||
'models': [model_dict],
|
||||
'weight':app_weight.get(app_label,99)
|
||||
}
|
||||
|
||||
app_list = list(six.itervalues(app_dict))
|
||||
app_list.sort(key=lambda x: x['weight'])
|
||||
|
||||
for app in app_list:
|
||||
app['models'].sort(key=lambda x: x['weight'])
|
||||
|
||||
context = dict(
|
||||
maxi_app_list=app_list,
|
||||
)
|
||||
try:
|
||||
todolist = self.get_my_task(request)
|
||||
context.update(dict(todolist = todolist))
|
||||
except Exception,e:
|
||||
pass
|
||||
# print context
|
||||
view_kwargs['extra_context'] = context
|
||||
|
||||
if view_func.__name__ == 'app_index':
|
||||
app_label = view_kwargs['app_label']
|
||||
app_name = apps.get_app_config(app_label).verbose_name
|
||||
app_dict = {}
|
||||
lib_dict = {}
|
||||
for model, model_admin in admin.site._registry.items():
|
||||
if model_admin.has_module_permission(request):
|
||||
label = model._meta.app_label
|
||||
is_current = False
|
||||
if label == app_label:
|
||||
is_current = True
|
||||
lib_dict[label] = {
|
||||
'name': apps.get_app_config(label).verbose_name,
|
||||
'app_label': label,
|
||||
'app_url': reverse(
|
||||
'admin:app_list',
|
||||
kwargs={'app_label': label},
|
||||
current_app=admin.site.name,
|
||||
),
|
||||
'weight':app_weight.get(label,99),
|
||||
'is_current':is_current,
|
||||
}
|
||||
if app_label == model._meta.app_label:
|
||||
has_module_perms = model_admin.has_module_permission(request)
|
||||
if not has_module_perms:
|
||||
raise PermissionDenied
|
||||
|
||||
perms = model_admin.get_model_perms(request)
|
||||
|
||||
if True in perms.values():
|
||||
info = (app_label, model._meta.model_name)
|
||||
model_dict = {
|
||||
'name': capfirst(model._meta.verbose_name_plural),
|
||||
'object_name': model._meta.object_name,
|
||||
'perms': perms,
|
||||
'weight':getattr(model,'index_weight',99)
|
||||
}
|
||||
if perms.get('change'):
|
||||
try:
|
||||
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=admin.site.name)
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
if perms.get('add'):
|
||||
try:
|
||||
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=admin.site.name)
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
if app_dict:
|
||||
app_dict['models'].append(model_dict),
|
||||
else:
|
||||
app_dict = {
|
||||
'name': app_name,
|
||||
'app_label': app_label,
|
||||
'app_url': '',
|
||||
'has_module_perms': has_module_perms,
|
||||
'models': [model_dict],
|
||||
}
|
||||
if not app_dict:
|
||||
raise Http404('The requested admin page does not exist.')
|
||||
# Sort the models alphabetically within each app.
|
||||
app_dict['models'].sort(key=lambda x: x['weight'])
|
||||
|
||||
app_lib = list(six.itervalues(lib_dict))
|
||||
app_lib.sort(key=lambda x: x['weight'])
|
||||
|
||||
context = dict(
|
||||
maxi_app_list=[app_dict],
|
||||
app_lib=app_lib,
|
||||
)
|
||||
view_kwargs['extra_context'] = context
|
||||
|
||||
def get_my_task(self,request):
|
||||
from workflow.models import TodoList
|
||||
if request and request.user:
|
||||
query = TodoList.objects.filter(user=request.user,status=0)
|
||||
if query.count() == 0:
|
||||
return None
|
||||
else:
|
||||
return query.all()[:10]
|
||||
@@ -0,0 +1,9 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import pymysql
|
||||
pymysql.install_as_MySQLdb()
|
||||
#解决mysql-python的历史遗留问题(Yuri_2017-04-29)
|
||||
from django.contrib import admin
|
||||
|
||||
admin.site.site_header = '智捷ERP'
|
||||
admin.site.site_title = '智捷ERP'
|
||||
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Django settings for mis project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.8.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.8/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.8/ref/settings/
|
||||
"""
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '_5%1a5zxdjsb-je@85!l34g--ve7!skhc%^c2n)3vqyhq)yq@c'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'syscfg',
|
||||
'basedata',
|
||||
'organ',
|
||||
'workflow',
|
||||
'selfhelp',
|
||||
'hr',
|
||||
'invent',
|
||||
'purchase',
|
||||
'sale',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'midware.cuser.RequestUser',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'mis.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'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',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
TEMPLATE_THEME = 'default'
|
||||
|
||||
WSGI_APPLICATION = 'mis.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'HOST': '172.31.16.165',
|
||||
'NAME': 'erp',
|
||||
'USER': 'maximus',
|
||||
'PASSWORD': 'maximus1234',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'zh-CN'
|
||||
|
||||
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = False
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR,'static')
|
||||
STATICFILES_DIRS = (
|
||||
('css',os.path.join(STATIC_ROOT,'css')),
|
||||
('js',os.path.join(STATIC_ROOT,'js')),
|
||||
('img',os.path.join(STATIC_ROOT,'img')),
|
||||
)
|
||||
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR,'upload')
|
||||
MEDIA_URL = '/upload/'
|
||||
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Django settings for mis project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.8.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.8/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.8/ref/settings/
|
||||
"""
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '_5%1a5zxdjsb-je@85!l34g--ve7!skhc%^c2n)3vqyhq)yq@c'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'syscfg',
|
||||
'basedata',
|
||||
'organ',
|
||||
'workflow',
|
||||
'selfhelp',
|
||||
'hr',
|
||||
'invent',
|
||||
'purchase',
|
||||
'sale',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'midware.cuser.RequestUser',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'mis.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'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',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
TEMPLATE_THEME = 'default'
|
||||
|
||||
WSGI_APPLICATION = 'mis.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'HOST': os.environ.get('DATABASE_HOST', 'localhost'),
|
||||
'PORT': os.environ.get('DATABASE_PORT', '3306'),
|
||||
'NAME': os.environ.get('DATABASE_NAME', 'mis'),
|
||||
'USER': os.environ.get('DATABASE_USER', 'root'),
|
||||
'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'root'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'zh-hans'
|
||||
|
||||
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
|
||||
|
||||
TIME_ZONE = 'Asia/Shanghai'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = False
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR,'static')
|
||||
STATICFILES_DIRS = (
|
||||
('css',os.path.join(STATIC_ROOT,'css')),
|
||||
('js',os.path.join(STATIC_ROOT,'js')),
|
||||
('img',os.path.join(STATIC_ROOT,'img')),
|
||||
)
|
||||
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR,'upload')
|
||||
MEDIA_URL = '/upload/'
|
||||
@@ -0,0 +1,21 @@
|
||||
from django.conf.urls import include, url, static
|
||||
from django.contrib import admin
|
||||
from mis import settings
|
||||
import workflow.views
|
||||
import invent.urls
|
||||
import basedata.urls
|
||||
import selfhelp.urls
|
||||
import mis.views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', mis.views.home),
|
||||
url(r"^admin/(?P<app>\w+)/(?P<model>\w+)/(?P<object_id>\d+)/start", workflow.views.start),
|
||||
url(r"^admin/(?P<app>\w+)/(?P<model>\w+)/(?P<object_id>\d+)/approve/(?P<operation>\d+)", workflow.views.approve),
|
||||
url(r"^admin/(?P<app>\w+)/(?P<model>\w+)/(?P<object_id>\d+)/restart/(?P<instance>\d+)", workflow.views.restart),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^admin/invent/', include(invent.urls)),
|
||||
url(r'^admin/basedata/', include(basedata.urls)),
|
||||
url(r'^admin/selfhelp/', include(selfhelp.urls)),
|
||||
]
|
||||
urlpatterns += static.static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static.static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.http.response import HttpResponseRedirect
|
||||
|
||||
|
||||
def home(request):
|
||||
return HttpResponseRedirect("/admin")
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for mis 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/1.8/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mis.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
@@ -0,0 +1 @@
|
||||
default_app_config = 'organ.apps.OrganConfig'
|
||||
@@ -0,0 +1,68 @@
|
||||
# coding=utf-8
|
||||
from django.contrib import admin
|
||||
from common import generic
|
||||
from organ.models import Organization,OrgUnit,Position
|
||||
from basedata.models import BankAccount
|
||||
from basedata.admin import BankAccountInline
|
||||
import datetime
|
||||
|
||||
|
||||
class OrgAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'O'
|
||||
CODE_NUMBER_WIDTH = 2
|
||||
list_display = ['code','name','represent','lic_code','cer_code']
|
||||
|
||||
fields = (
|
||||
('name','code',),('short','pinyin',),
|
||||
('tax_num','tax_account',),('tax_address',),('represent','email',),
|
||||
('address','zipcode',),('contacts','phone',),('fax','website',),
|
||||
('lic_code','cer_code',),('license','certificate',),('status','weight',),
|
||||
)
|
||||
|
||||
inlines = [BankAccountInline]
|
||||
|
||||
|
||||
class OrgUnitAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'S'
|
||||
CODE_NUMBER_WIDTH = 3
|
||||
list_display = ['code','name','unit_type','parent']
|
||||
list_display_links = ['code','name']
|
||||
fields = (
|
||||
('organization',),('parent',),('name','code',),('short','pinyin',),
|
||||
('unit_type',),('status','virtual',),('phone','fax',),
|
||||
('contacts','email',),('weight',),
|
||||
)
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super(OrgUnitAdmin,self).get_queryset(request).filter(end__gt=datetime.date.today())
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj.parent and obj.parent.organization:
|
||||
obj.organization = obj.parent.organization
|
||||
obj.save()
|
||||
super(OrgUnitAdmin,self).save_model(request,obj,form,change)
|
||||
|
||||
|
||||
class PositionAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'P'
|
||||
CODE_NUMBER_WIDTH = 4
|
||||
list_display = ['code','name','unit','series','grade','parent']
|
||||
list_display_links = ['code','name']
|
||||
fields = (
|
||||
('unit',),('organization',),('parent',),('name','code',),('short','pinyin',),('series','grade',),
|
||||
('virtual','status'),('description',),('qualification',),('document',),('weight',),
|
||||
)
|
||||
readonly_fields = ['organization']
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super(PositionAdmin,self).get_queryset(request).filter(end__gt=datetime.date.today())
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj.unit:
|
||||
obj.organization = obj.unit.organization
|
||||
obj.save()
|
||||
super(PositionAdmin,self).save_model(request,obj,form,change)
|
||||
|
||||
admin.site.register(Position,PositionAdmin)
|
||||
admin.site.register(OrgUnit,OrgUnitAdmin)
|
||||
admin.site.register(Organization,OrgAdmin)
|
||||
@@ -0,0 +1,9 @@
|
||||
__author__ = 'zhugl'
|
||||
# created at 15-4-22
|
||||
from django.apps.config import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class OrganConfig(AppConfig):
|
||||
name = 'organ'
|
||||
verbose_name = _('organization')
|
||||
@@ -0,0 +1,115 @@
|
||||
# coding=utf-8
|
||||
from django.db import models
|
||||
from django.db import connection
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import const
|
||||
from common import generic
|
||||
|
||||
|
||||
class Organization(generic.BO):
|
||||
"""
|
||||
组织单位
|
||||
"""
|
||||
index_weight = 1
|
||||
code = models.CharField(_("organ code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
name = models.CharField(_("organ name"),max_length=const.DB_CHAR_NAME_120)
|
||||
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
|
||||
tax_num = models.CharField(_("tax num"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
tax_address = models.CharField(_("tax address"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
tax_account = models.CharField(_("tax account"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
|
||||
represent = models.CharField(_("representative "),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
address = models.CharField(_("address"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
zipcode = models.CharField(_("zipcode"),max_length=const.DB_CHAR_CODE_8,blank=True,null=True)
|
||||
fax = models.CharField(_("fax"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
contacts = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
website = models.CharField(_("website"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
email = models.CharField(_("email"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
lic_code = models.CharField(_("license code"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
cer_code = models.CharField(_("certificate code"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
license = models.FileField(_("business license"),blank=True,null=True,upload_to='organ')
|
||||
certificate = models.FileField(_("organization code certificate"),blank=True,null=True,upload_to='organ')
|
||||
weight = models.IntegerField(_("weight"),default=9)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('organization')
|
||||
verbose_name_plural = _('organization')
|
||||
|
||||
|
||||
class OrgUnit(generic.BO):
|
||||
"""
|
||||
组织单元
|
||||
"""
|
||||
UNIT_LEVEL = (
|
||||
(1,_('BRANCH')),
|
||||
(2,_('DEPARTMENT')),
|
||||
(3,_('OFFICE')),
|
||||
(4,_('TEAM')),
|
||||
(5,_('COMMITTEE'))
|
||||
)
|
||||
index_weight = 2
|
||||
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||
organization = models.ForeignKey(Organization,verbose_name = _('organization'),null=True,blank=True)
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_8,blank=True,null=True)
|
||||
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
unit_type = models.IntegerField(_("type"),choices=UNIT_LEVEL,default=2)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
virtual = models.BooleanField(_("is virtual"),default=False)
|
||||
fax = models.CharField(_("fax"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
contacts = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
email = models.CharField(_("email"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
weight = models.IntegerField(_("weight"),default=99)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('org unit')
|
||||
verbose_name_plural = _('org unit')
|
||||
|
||||
|
||||
class Position(generic.BO):
|
||||
"""
|
||||
岗位
|
||||
"""
|
||||
SERIES = (
|
||||
('A',_("Admin Position")),
|
||||
('S',_("Sale Position")),
|
||||
('T',_("Technology Position")),
|
||||
('P',_("Produce Position")),
|
||||
)
|
||||
|
||||
GRADE = (
|
||||
('01', _("BASIC")),
|
||||
('02', _("MEDIUM")),
|
||||
('03', _("SENIOR")),
|
||||
('04', _("PROFESSOR")),
|
||||
('05', _("EXPERT")),
|
||||
)
|
||||
index_weight = 3
|
||||
unit = models.ForeignKey(OrgUnit,verbose_name=_('org unit'))
|
||||
organization = models.ForeignKey(Organization,verbose_name=_('organization'),null=True,blank=True)
|
||||
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||
code = models.CharField(_("position code"),max_length=const.DB_CHAR_CODE_8,blank=True,null=True)
|
||||
name = models.CharField(_("position name"),max_length=const.DB_CHAR_NAME_120)
|
||||
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
series = models.CharField(_("position series"),max_length=1,default='A',choices=const.get_value_list('S014'))
|
||||
grade = models.CharField(_("position grade"),max_length=const.DB_CHAR_CODE_2,default='01',choices=const.get_value_list('S015'))
|
||||
virtual = models.BooleanField(_("is virtual"),default=False)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
description = models.TextField(_("position description"),blank=True,null=True)
|
||||
qualification = models.TextField(_("qualification"),blank=True,null=True)
|
||||
document = models.FileField(_("reference"),blank=True,null=True)
|
||||
weight = models.IntegerField(_("weight"),default=99)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s %s' % (self.code,self.name)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('position')
|
||||
verbose_name_plural = _('position')
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -0,0 +1,3 @@
|
||||
# created at 15-6-27
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
@@ -0,0 +1,54 @@
|
||||
# created at 15-6-30
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
|
||||
|
||||
class Operation(object):
|
||||
APPROVE = 1
|
||||
DENY = 3
|
||||
TERMINATE = 4
|
||||
|
||||
|
||||
class WorkflowAction(object):
|
||||
|
||||
name = ''
|
||||
description = ''
|
||||
|
||||
def action(self,request,obj,node_config,operation=Operation.APPROVE):
|
||||
"""
|
||||
|
||||
:param request:
|
||||
:param obj:
|
||||
:param node_config:
|
||||
:return:
|
||||
"""
|
||||
|
||||
|
||||
class TestAction(WorkflowAction):
|
||||
name = 'action.test'
|
||||
|
||||
def action(self,request,obj,node_config,operation=Operation.APPROVE):
|
||||
print 'this is a workflow test action'
|
||||
print 'request user is %s,current node is %s'%(request.user,node_config)
|
||||
|
||||
|
||||
class WorkflowActionManager(object):
|
||||
"""
|
||||
|
||||
"""
|
||||
actions = {}
|
||||
registed = False
|
||||
|
||||
def __init__(self):
|
||||
if WorkflowActionManager.registed:
|
||||
pass
|
||||
else:
|
||||
WorkflowActionManager.register(TestAction)
|
||||
WorkflowActionManager.registed = True
|
||||
|
||||
@classmethod
|
||||
def register(cls,action):
|
||||
if cls.actions.get(action.name):
|
||||
raise Exception('%s already exists,register failed'%action.name)
|
||||
if issubclass(action,WorkflowAction):
|
||||
WorkflowActionManager.actions[action.name] = action()
|
||||
@@ -0,0 +1,54 @@
|
||||
# created at 15-6-30
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
from workflow.models import Node
|
||||
|
||||
|
||||
class NextNodeHandler(object):
|
||||
"""
|
||||
|
||||
"""
|
||||
name = ''
|
||||
description = ''
|
||||
|
||||
def handle(self,request,obj,node_config):
|
||||
"""
|
||||
|
||||
:param request:
|
||||
:param obj:
|
||||
:param node_config:
|
||||
:return:workflow.models.Node
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class TestHandler(NextNodeHandler):
|
||||
name = 'project.budge.gt.10000'
|
||||
description = '预算金额大于10000,由总经理审批'
|
||||
|
||||
def handle(self,request,obj,node_config):
|
||||
budget = getattr(obj,'budget',None)
|
||||
if budget and budget > 10000:
|
||||
return Node.objects.filter(id=7).all()
|
||||
|
||||
|
||||
class NextNodeManager(object):
|
||||
"""
|
||||
|
||||
"""
|
||||
handlers = {}
|
||||
registed = False
|
||||
|
||||
def __init__(self):
|
||||
if NextNodeManager.registed:
|
||||
pass
|
||||
else:
|
||||
NextNodeManager.register(TestHandler)
|
||||
NextNodeManager.registed = True
|
||||
|
||||
@classmethod
|
||||
def register(cls,handler):
|
||||
if cls.handlers.get(handler.name):
|
||||
raise Exception('%s already exists,register failed'%handler.name)
|
||||
if issubclass(handler,NextNodeHandler):
|
||||
NextNodeManager.handlers[handler.name] = handler()
|
||||
@@ -0,0 +1,67 @@
|
||||
# created at 15-6-30
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
|
||||
|
||||
class NextUserHandler(object):
|
||||
"""
|
||||
|
||||
"""
|
||||
name = ''
|
||||
description = ''
|
||||
|
||||
def handle(self,request,obj,node_config):
|
||||
"""
|
||||
|
||||
:param request:
|
||||
:param obj:
|
||||
:param node_config:
|
||||
:return:django.contrib.auth.models.User[]
|
||||
"""
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class UpPosition(NextUserHandler):
|
||||
"""
|
||||
|
||||
"""
|
||||
name = 'up.position.user'
|
||||
|
||||
def handle(self,request,obj,node_config):
|
||||
from basedata.models import Employee,Position
|
||||
emp_query = Employee.objects.filter(user=request.user)
|
||||
if emp_query.count()>0:
|
||||
emp = emp_query.all()
|
||||
parent = []
|
||||
for e in emp:
|
||||
if e.position and e.position.parent:
|
||||
parent.append(e.position.parent)
|
||||
# print emp
|
||||
# print parent
|
||||
query2 = Employee.objects.filter(position__in=parent).exclude(user=None)
|
||||
return [x.user for x in query2.all()]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class NextUserManager(object):
|
||||
"""
|
||||
|
||||
"""
|
||||
handlers = {}
|
||||
registed = False
|
||||
|
||||
def __init__(self):
|
||||
if NextUserManager.registed:
|
||||
pass
|
||||
else:
|
||||
NextUserManager.register(UpPosition)
|
||||
NextUserManager.registed = True
|
||||
|
||||
@classmethod
|
||||
def register(cls,handler):
|
||||
if cls.handlers.get(handler.name):
|
||||
raise Exception('%s already exists,register failed'%handler.name)
|
||||
if issubclass(handler,NextUserHandler):
|
||||
NextUserManager.handlers[handler.name] = handler()
|
||||
@@ -0,0 +1,183 @@
|
||||
# created at 15-6-27
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
import xlrd
|
||||
import os
|
||||
import datetime
|
||||
from mis import settings
|
||||
|
||||
|
||||
class Handler(object):
|
||||
name = ''
|
||||
|
||||
def handle(self,obj,f):
|
||||
pass
|
||||
|
||||
|
||||
class OPSHandler(Handler):
|
||||
"""
|
||||
导入基础信息:部门,岗位,职员
|
||||
"""
|
||||
name = 'OPS'
|
||||
|
||||
def handle(self,obj,f):
|
||||
if f and f.name.endswith('.xls'):
|
||||
path = os.path.join(settings.MEDIA_ROOT,f.name)
|
||||
workbook = xlrd.open_workbook(path)
|
||||
for sheet in workbook.sheets():
|
||||
if sheet.name == u'部门' or sheet.name == 'department':
|
||||
self.department(obj,sheet)
|
||||
elif sheet.name == u'岗位' or sheet.name == 'position':
|
||||
self.position(obj,sheet)
|
||||
elif sheet.name == u'职员' or sheet.name == 'employee':
|
||||
self.stuff(obj,sheet)
|
||||
|
||||
def department(self,obj,sheet):
|
||||
from organ.models import OrgUnit
|
||||
row_count = sheet.nrows
|
||||
if obj.is_clear:
|
||||
OrgUnit.objects.update(end=datetime.date.today())
|
||||
for row_index in range(1,row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
weight = 99
|
||||
if row[6]:
|
||||
weight = row[6]
|
||||
OrgUnit.objects.create(code=row[0],name=row[1],short=row[2],pinyin=row[3],begin=datetime.date.today(),
|
||||
end=datetime.date(9999,12,31),weight=weight)
|
||||
for row_index in range(1,row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
if len(row[4]) > 0:
|
||||
try:
|
||||
parent = OrgUnit.objects.get(code=row[4])
|
||||
OrgUnit.objects.filter(code=row[0]).update(parent=parent)
|
||||
except Exception,e:
|
||||
pass
|
||||
|
||||
def position(self,obj,sheet):
|
||||
from organ.models import Position,OrgUnit
|
||||
row_count = sheet.nrows
|
||||
if obj.is_clear:
|
||||
Position.objects.update(end=datetime.date.today())
|
||||
for row_index in range(1,row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
depart = None
|
||||
if len(row[4]) > 0:
|
||||
try:
|
||||
depart = OrgUnit.objects.filter(code=row[4],end__gt=datetime.date.today()).all()[0]
|
||||
except Exception,e:
|
||||
pass
|
||||
weight = 99
|
||||
if row[6]:
|
||||
weight = row[6]
|
||||
Position.objects.create(code=row[0],name=row[1],unit=depart,begin=datetime.date.today(),
|
||||
end=datetime.date(9999,12,31),weight=weight)
|
||||
for row_index in range(1,row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
if len(row[2]) > 0:
|
||||
try:
|
||||
parent = Position.objects.filter(code=row[2],end__gt=datetime.date.today()).all()[0]
|
||||
Position.objects.filter(code=row[0]).update(parent=parent)
|
||||
except Exception,e:
|
||||
pass
|
||||
|
||||
def stuff(self,obj,sheet):
|
||||
from organ.models import Position
|
||||
from basedata.models import Employee
|
||||
from django.contrib.auth.models import User,Group
|
||||
row_count = sheet.nrows
|
||||
try:
|
||||
group = Group.objects.get_by_natural_key(u'职员')
|
||||
except Exception,e:
|
||||
pass
|
||||
for row_index in range(1,row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
position = Position.objects.filter(code=row[8],end__gt=datetime.date.today()).all()[0]
|
||||
username = row[10]
|
||||
email = row[11]
|
||||
password = row[4][-6:]
|
||||
if position is None:
|
||||
raise Exception(u'职员%s-%s未分配岗位,或者您选择的岗位已失效,不可被引用'%(row[0],row[1]))
|
||||
try:
|
||||
employee = Employee.objects.get(code=row[0])
|
||||
if employee.position.code == row[8]:
|
||||
continue
|
||||
else:
|
||||
employee.position = position
|
||||
employee.save()
|
||||
except Exception,e:
|
||||
employee = Employee.objects.create(code=row[0],name=row[1],pinyin=row[2],gender=row[3],idcard=row[4],
|
||||
birthday=row[5],workday=row[6],startday=row[7],position=position)
|
||||
if username:
|
||||
try:
|
||||
user = User.objects.get_by_natural_key(username)
|
||||
except Exception,e:
|
||||
user = User.objects.create_user(username=username,password=password)
|
||||
user.is_staff = True
|
||||
user.is_active = True
|
||||
user.first_name = row[1]
|
||||
if email:
|
||||
user.email = email
|
||||
if group:
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
employee.user = user
|
||||
employee.save()
|
||||
|
||||
|
||||
class UserHandler(Handler):
|
||||
"""
|
||||
基础数据导入:用户
|
||||
"""
|
||||
name = 'admin.user'
|
||||
|
||||
def handle(self,obj,f):
|
||||
from django.contrib.auth.models import User,Group
|
||||
if f and f.name.endswith('.xls'):
|
||||
path = os.path.join(settings.MEDIA_ROOT,f.name)
|
||||
workbook = xlrd.open_workbook(path)
|
||||
sheet = workbook.sheet_by_index(0)
|
||||
row_count = sheet.nrows
|
||||
try:
|
||||
group = Group.objects.get_by_natural_key(u'职员')
|
||||
except Exception,e:
|
||||
pass
|
||||
|
||||
for row_index in range(2,row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
username = row[0]
|
||||
password = row[1]
|
||||
last_name = row[2]
|
||||
first_name = row[3]
|
||||
email = row[4]
|
||||
if username == '':
|
||||
continue
|
||||
try:
|
||||
user = User.objects.get_by_natural_key(username)
|
||||
except Exception,e:
|
||||
user = User.objects.create_user(username=username,password=password,email=email)
|
||||
user.is_staff = True
|
||||
user.is_active = True
|
||||
user.last_name = last_name
|
||||
user.first_name = first_name
|
||||
if group:
|
||||
user.groups.add(group)
|
||||
user.save()
|
||||
|
||||
|
||||
class ExcelManager(object):
|
||||
"""
|
||||
|
||||
"""
|
||||
handlers = {}
|
||||
|
||||
def __init__(self):
|
||||
ExcelManager.register(OPSHandler)
|
||||
ExcelManager.register(UserHandler)
|
||||
|
||||
@classmethod
|
||||
def register(cls,handler):
|
||||
if cls.handlers.get(handler.name):
|
||||
raise Exception('%s already exists,register failed'%handler.name)
|
||||
if issubclass(handler,Handler):
|
||||
ExcelManager.handlers[handler.name] = handler()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
default_app_config = "purchase.apps.MyAppConfig"
|
||||
@@ -0,0 +1,90 @@
|
||||
from django.contrib import admin
|
||||
from common import generic
|
||||
from purchase.models import PurchaseOrder,POItem,Invoice,Payment
|
||||
from basedata.models import Partner,BankAccount
|
||||
|
||||
|
||||
class POItemInline(admin.TabularInline):
|
||||
model = POItem
|
||||
fields = ('material', 'measure','price', 'cnt', 'tax', 'discount_price')
|
||||
raw_id_fields = ['material']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 3
|
||||
|
||||
|
||||
class PurchaseOrderAdmin(generic.BOAdmin):
|
||||
"""
|
||||
|
||||
"""
|
||||
CODE_PREFIX = 'CG'
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
list_display = ['code','title','order_date','partner','amount','discount_amount','payed_amount','invoice_amount','status']
|
||||
list_display_links = ['code','title']
|
||||
raw_id_fields = ['partner']
|
||||
fields = (
|
||||
('code','partner'),('order_date','arrive_date'),
|
||||
('title','status',),('description',),('amount','discount_amount'),('attach',),
|
||||
)
|
||||
readonly_fields = ['status','amount']
|
||||
inlines = [POItemInline]
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
if object_id:
|
||||
extra_context = extra_context or {}
|
||||
obj = PurchaseOrder.objects.get(id=object_id)
|
||||
if obj.status == '99':
|
||||
extra_context.update(dict(readonly=True))
|
||||
return super(PurchaseOrderAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
def get_changeform_initial_data(self, request):
|
||||
import datetime
|
||||
begin = datetime.date.today()
|
||||
end = begin + datetime.timedelta(30)
|
||||
return {'order_date':begin,'arrive_date':end}
|
||||
|
||||
|
||||
class PurchaseItemAdmin(generic.BOAdmin):
|
||||
list_display = ['po','vender','material','cnt','price','tax']
|
||||
readonly_fields = ['po','material','cnt','price','tax']
|
||||
|
||||
def get_queryset(self, request):
|
||||
return POItem.objects.filter(left_cnt__gt=0)
|
||||
|
||||
|
||||
class InvoiceAdmin(generic.BOAdmin):
|
||||
list_display = ['code','number','vo_date','po','partner','po_amount','vo_amount']
|
||||
readonly_fields = ['partner','po_amount']
|
||||
raw_id_fields = ['po']
|
||||
search_fields = ['po__code','partner__name']
|
||||
date_hierarchy = 'begin'
|
||||
fields = (
|
||||
('vo_date',),('po',),('po_amount','partner',),('code',),('number',),('vo_amount',),('file',)
|
||||
)
|
||||
|
||||
|
||||
class PaymentAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'PY'
|
||||
CODE_NUMBER_WIDTH = 4
|
||||
list_display = ['code','py_date','po','partner','po_amount','py_amount']
|
||||
readonly_fields = ['partner','po_amount']
|
||||
raw_id_fields = ['po']
|
||||
search_fields = ['po__code','partner__name']
|
||||
fields = (
|
||||
('py_date','code',),('po',),('partner','po_amount',),('py_amount',),('bank',),('response_code'),('memo',)
|
||||
)
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||
if db_field.name == 'bank':
|
||||
kwargs['queryset'] = BankAccount.objects.exclude(org__exact=None)
|
||||
return super(PaymentAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||
|
||||
admin.site.register(PurchaseOrder,PurchaseOrderAdmin)
|
||||
admin.site.register(POItem,PurchaseItemAdmin)
|
||||
admin.site.register(Invoice,InvoiceAdmin)
|
||||
admin.site.register(Payment,PaymentAdmin)
|
||||
@@ -0,0 +1,11 @@
|
||||
# created at 15-5-23
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class MyAppConfig(AppConfig):
|
||||
name = 'purchase'
|
||||
verbose_name = _("purchase manage")
|
||||
@@ -0,0 +1,221 @@
|
||||
# coding=utf-8
|
||||
import datetime
|
||||
import os
|
||||
import xlrd
|
||||
import decimal
|
||||
from django.db import transaction
|
||||
from django.db import models
|
||||
from django.db.models.aggregates import Sum
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.text import force_text
|
||||
from mis import settings
|
||||
from common import generic
|
||||
from common import const
|
||||
from selfhelp.models import WOItem
|
||||
from basedata.models import Material,Organization,Partner,Measure,BankAccount
|
||||
|
||||
|
||||
class PurchaseOrder(generic.BO):
|
||||
"""
|
||||
|
||||
"""
|
||||
STATUS = (
|
||||
('0', _("NEW")),
|
||||
('1', _("IN PROGRESS")),
|
||||
('4', _("DROP")),
|
||||
('9', _("APPROVED")),
|
||||
('99', _("ALREADY STOCK IN")),
|
||||
)
|
||||
index_weight = 1
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
partner = models.ForeignKey(Partner,verbose_name=_("partner"),limit_choices_to={"partner_type":"S"})
|
||||
order_date = models.DateField(_("order date"))
|
||||
arrive_date = models.DateField(_("arrive date"))
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
amount = models.DecimalField(_("money amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||
discount_amount = models.DecimalField(_("discount amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||
entry_status = models.BooleanField(_("entry status"),default=0)
|
||||
entry_time = models.DateTimeField(_("entry time"),blank=True,null=True)
|
||||
attach = models.FileField(_('attach'),blank=True,null=True,help_text=u'您可导入采购明细,模板请参考文档FD0008')
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s %s' % (self.code,self.title)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
super(PurchaseOrder,self).save(force_insert,force_update,using,update_fields)
|
||||
if self.discount_amount > 0:
|
||||
sql = 'UPDATE purchase_poitem a SET a.discount_price = ' \
|
||||
'a.price-((SELECT discount_amount/amount FROM purchase_purchaseorder WHERE id=%s)*a.amount/a.cnt) WHERE a.po_id = %s'
|
||||
params = [self.id,self.id]
|
||||
generic.update(sql,params)
|
||||
|
||||
item_count = POItem.objects.filter(po=self).count()
|
||||
if self.attach and item_count == 0:
|
||||
path = os.path.join(settings.MEDIA_ROOT,self.attach.name)
|
||||
workbook = xlrd.open_workbook(path)
|
||||
sheet = workbook.sheet_by_index(0)
|
||||
row_count = sheet.nrows
|
||||
with transaction.atomic():
|
||||
total_amount = decimal.Decimal(0)
|
||||
for row_index in range(row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
if row_index == 0:
|
||||
doc_type = row[1]
|
||||
if doc_type.startswith('0'):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
elif row_index < 3:
|
||||
continue
|
||||
|
||||
material = None
|
||||
measure = None
|
||||
|
||||
try:
|
||||
measure = Measure.objects.get(code=row[4])
|
||||
except Exception,e:
|
||||
measure = Measure.objects.create(code=row[4],name=force_text(row[5]))
|
||||
|
||||
try:
|
||||
material = Material.objects.get(code=row[0])
|
||||
except Exception,e:
|
||||
material = Material(code=row[0],name=force_text(row[1]),spec=force_text(row[2]))
|
||||
material.purchase_price = row[6]
|
||||
material.save()
|
||||
amount = decimal.Decimal(row[6])*decimal.Decimal(row[7])
|
||||
POItem.objects.create(po=self,material=material,measure=measure,cnt=row[7],price=row[6],amount=amount)
|
||||
total_amount += amount
|
||||
sql = 'update purchase_purchaseorder set amount = %s where id=%s'
|
||||
params = [total_amount,self.id]
|
||||
generic.update(sql,params)
|
||||
|
||||
def invoice_amount(self):
|
||||
total = Invoice.objects.filter(po=self).aggregate(Sum('vo_amount')).get('vo_amount__sum') or 0.00
|
||||
return total
|
||||
|
||||
def payed_amount(self):
|
||||
return Payment.objects.filter(po=self).aggregate(Sum('py_amount')).get('py_amount__sum') or 0.00
|
||||
|
||||
invoice_amount.short_description = _('invoice amount')
|
||||
payed_amount.short_description = _('pay amount')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("purchase order")
|
||||
verbose_name_plural = _("purchase orders")
|
||||
|
||||
|
||||
class POItem(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
index_weight = 2
|
||||
po = models.ForeignKey(PurchaseOrder,verbose_name=_("purchase order"))
|
||||
material = models.ForeignKey(Material,verbose_name=_("material"),limit_choices_to={"is_virtual":"0"})
|
||||
measure = models.ForeignKey(Measure,verbose_name=_("measure"),blank=True,null=True)
|
||||
price = models.DecimalField(_("price"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||
cnt = models.DecimalField(_("count"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||
discount_price = models.DecimalField(_("discount price"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||
amount = models.DecimalField(_("money of amount"),max_digits=12,decimal_places=2,blank=True,null=True)
|
||||
discount_amount = models.DecimalField(_("discount amount"),max_digits=12,decimal_places=2,blank=True,null=True)
|
||||
tax = models.CharField(_("tax rate"),max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S052'),default='0.00')
|
||||
woitem = models.ForeignKey(WOItem,verbose_name=_("wo item"),blank=True,null=True)
|
||||
is_in_stock = models.BooleanField(_("is in stock"),default=0)
|
||||
in_stock_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||
entry_cnt = models.DecimalField(_("entry count"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||
left_cnt = models.DecimalField(_("left count"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if self.price and self.cnt:
|
||||
money = self.price * self.cnt
|
||||
self.amount = money
|
||||
|
||||
if self.measure is None and self.material and self.material.measure.count() > 0:
|
||||
self.measure = self.material.measure.all()[0]
|
||||
|
||||
if self.is_in_stock:
|
||||
self.left_cnt -= self.entry_cnt
|
||||
else:
|
||||
self.left_cnt = self.cnt
|
||||
super(POItem,self).save(force_insert,force_update,using,update_fields)
|
||||
self.material.purchase_price = self.price
|
||||
self.material.save()
|
||||
sql = 'UPDATE purchase_purchaseorder SET amount = (SELECT SUM(a.price*a.cnt) AS amount FROM ' \
|
||||
'purchase_poitem a WHERE a.po_id = %s) WHERE id = %s'
|
||||
params = [self.po.id,self.po.id]
|
||||
generic.update(sql,params)
|
||||
|
||||
def vender(self):
|
||||
return u'%s' % (self.po.partner)
|
||||
|
||||
vender.short_description = _("partner")
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("po item")
|
||||
verbose_name_plural = _("po item")
|
||||
|
||||
|
||||
class Invoice(generic.BO):
|
||||
"""
|
||||
采购发票
|
||||
"""
|
||||
index_weight = 4
|
||||
vo_date = models.DateField(_("invoice date"),blank=True,null=True,default=datetime.date.today)
|
||||
code = models.CharField(_("invoice code"),max_length=const.DB_CHAR_NAME_20)
|
||||
number = models.CharField(_("invoice number"),max_length=const.DB_CHAR_NAME_20)
|
||||
po = models.ForeignKey(PurchaseOrder,verbose_name=_("purchase order"))
|
||||
partner = models.ForeignKey(Partner,verbose_name=_("partner"),blank=True,null=True)
|
||||
po_amount = models.DecimalField(_("po amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
vo_amount = models.DecimalField(_("invoice amount"),max_digits=14,decimal_places=4)
|
||||
file = models.FileField(_("invoice file"),upload_to='invoice',blank=True,null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s %s" % (self.code,self.partner)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if self.po:
|
||||
self.partner = self.po.partner
|
||||
self.po_amount = self.po.amount
|
||||
super(Invoice,self).save(force_insert,force_update,using,update_fields)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Invoice")
|
||||
verbose_name_plural = _("Invoice")
|
||||
|
||||
|
||||
class Payment(generic.BO):
|
||||
"""
|
||||
采购付款
|
||||
"""
|
||||
index_weight = 3
|
||||
py_date = models.DateField(_("pay date"),blank=True,null=True,default=datetime.date.today)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
code = models.CharField(_("pay code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
po = models.ForeignKey(PurchaseOrder,verbose_name=_("purchase order"))
|
||||
partner = models.ForeignKey(Partner,verbose_name=_("partner"),blank=True,null=True)
|
||||
po_amount = models.DecimalField(_("po amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
py_amount = models.DecimalField(_("pay amount"),max_digits=14,decimal_places=4)
|
||||
bank = models.ForeignKey(BankAccount,verbose_name=_("bank account"),blank=True,null=True)
|
||||
response_code = models.CharField(_("response code"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
memo = models.TextField(_("memo"),blank=True,null=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if self.po:
|
||||
self.partner = self.po.partner
|
||||
self.po_amount = self.po.amount
|
||||
super(Payment,self).save(force_insert,force_update,using,update_fields)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s %s" % (self.code,self.partner)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Payment")
|
||||
verbose_name_plural = _("Payment")
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -0,0 +1 @@
|
||||
default_app_config = "sale.apps.MyAppConfig"
|
||||
@@ -0,0 +1,104 @@
|
||||
from django.contrib import admin
|
||||
from common import generic
|
||||
from sale.models import SaleOrder,SaleItem,PaymentCollection,OfferSheet,OfferItem
|
||||
from basedata.models import Measure,BankAccount
|
||||
from common import generic
|
||||
import datetime
|
||||
|
||||
|
||||
class SaleItemInline(admin.TabularInline):
|
||||
model = SaleItem
|
||||
fields = ('material','measure','sale_price','discount_price','cnt','tax')
|
||||
raw_id_fields = ['material']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 3
|
||||
|
||||
|
||||
class OfferItemInline(admin.TabularInline):
|
||||
model = OfferItem
|
||||
fields = ('material','brand','measure','cost_price','sale_price','discount_price','cnt','tax')
|
||||
raw_id_fields = ['material']
|
||||
readonly_fields = ['brand']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 3
|
||||
|
||||
|
||||
class SaleOrderAdmin(generic.BOAdmin):
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
CODE_PREFIX = 'SO'
|
||||
inlines = [SaleItemInline]
|
||||
list_display = ['code','title','order_date','partner','amount','collection_amount']
|
||||
list_display_links = ['code','title']
|
||||
raw_id_fields = ['partner','user','org']
|
||||
fields = (
|
||||
('code','org',),('title','invoice_type',),('partner','user',),('order_date','deliver_date',),
|
||||
('contact','phone',),('deliver_address','fax',),('description',),('amount','discount_amount','status')
|
||||
)
|
||||
readonly_fields = ['amount','status']
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj:
|
||||
obj.user = request.user
|
||||
super(SaleOrderAdmin,self).save_model(request,obj,form,change)
|
||||
|
||||
def get_changeform_initial_data(self, request):
|
||||
today = datetime.datetime.today()
|
||||
deadline = today+datetime.timedelta(days=30)
|
||||
return {'order_date':today,'deliver_date':deadline}
|
||||
|
||||
|
||||
class OfferSheetAdmin(generic.BOAdmin):
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
CODE_PREFIX = 'BJ'
|
||||
inlines = [OfferItemInline]
|
||||
list_display = ['code','title','offer_date','partner','amount']
|
||||
list_display_links = ['code','title']
|
||||
raw_id_fields = ['partner','user','org']
|
||||
fields = (
|
||||
('code','org',),('partner','user',),('offer_date','deliver_date',),('title',),
|
||||
('description',),('amount','discount_amount',),('attach'),
|
||||
)
|
||||
readonly_fields = ['amount']
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj:
|
||||
obj.user = request.user
|
||||
super(OfferSheetAdmin,self).save_model(request,obj,form,change)
|
||||
|
||||
def get_changeform_initial_data(self, request):
|
||||
today = datetime.datetime.today()
|
||||
deadline = today+datetime.timedelta(days=30)
|
||||
return {'offer_date':today,'deliver_date':deadline}
|
||||
|
||||
|
||||
class PaymentCollectionAdmin(generic.BOAdmin):
|
||||
CODE_NUMBER_WIDTH = 4
|
||||
CODE_PREFIX = 'CP'
|
||||
list_display = ['code','so','partner','order_amount','collection_date','collection_amount']
|
||||
fields = (
|
||||
('code',),('collection_date',),('so',),('partner',),('order_amount','collection_amount',),('bank',),
|
||||
('memo',)
|
||||
)
|
||||
raw_id_fields = ['so','partner']
|
||||
readonly_fields = ['order_amount']
|
||||
list_display_links = ['code','so']
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||
if db_field.name=='bank':
|
||||
kwargs['queryset'] = BankAccount.objects.exclude(org__exact=None)
|
||||
return super(PaymentCollectionAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||
|
||||
admin.site.register(SaleOrder,SaleOrderAdmin)
|
||||
admin.site.register(PaymentCollection,PaymentCollectionAdmin)
|
||||
admin.site.register(OfferSheet,OfferSheetAdmin)
|
||||
@@ -0,0 +1,11 @@
|
||||
# created at 15-5-23
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class MyAppConfig(AppConfig):
|
||||
name = 'sale'
|
||||
verbose_name = _("sale management")
|
||||
@@ -0,0 +1,252 @@
|
||||
# coding=utf-8
|
||||
import decimal
|
||||
import datetime
|
||||
import csv
|
||||
import os
|
||||
import xlrd
|
||||
from mis import settings
|
||||
from django.db import models
|
||||
from django.db import transaction
|
||||
from django.db.models.aggregates import Sum
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.text import force_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import generic
|
||||
from common import const
|
||||
from basedata.models import Material,Project,Partner,Organization,Measure,BankAccount
|
||||
|
||||
|
||||
class SaleOrder(generic.BO):
|
||||
"""
|
||||
销售订单
|
||||
"""
|
||||
STATUS = (
|
||||
('0', _("NEW")),
|
||||
('1', _("IN PROGRESS")),
|
||||
('4', _("DROP")),
|
||||
('9', _("APPROVED")),
|
||||
('99', _("ALREADY STOCK OUT")),
|
||||
)
|
||||
index_weight = 2
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
partner = models.ForeignKey(Partner,verbose_name=_("partner"),limit_choices_to={"partner_type":"C"})
|
||||
order_date = models.DateField(_("order date"))
|
||||
deliver_date = models.DateField(_("deliver date"))
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||
description = models.TextField(_("memo"),blank=True,null=True)
|
||||
contact = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
fax = models.CharField(_("fax"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
deliver_address = models.CharField(_("deliver address"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
invoice_type = models.CharField(_("invoice type"),max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S053'),default='10')
|
||||
|
||||
amount = models.DecimalField(_("money amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||
discount_amount = models.DecimalField(_("discount amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||
user = models.ForeignKey(User,verbose_name=_("sales man"),blank=True,null=True)
|
||||
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s %s' % (self.code,self.title)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
super(SaleOrder,self).save(force_insert,force_update,using,update_fields)
|
||||
if self.discount_amount > 0:
|
||||
sql = 'UPDATE sale_saleitem a SET a.discount_price = a.sale_price - ' \
|
||||
'((SELECT discount_amount/amount FROM sale_saleorder WHERE id = %s) * (a.sale_price*a.cnt)/a.cnt) WHERE a.master_id = %s'
|
||||
params = [self.id,self.id]
|
||||
generic.update(sql,params)
|
||||
|
||||
def collection_amount(self):
|
||||
return PaymentCollection.objects.filter(so=self).aggregate(Sum('collection_amount')).get('collection_amount__sum') or 0.00
|
||||
|
||||
collection_amount.short_description = _('collection amount')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('sale order')
|
||||
verbose_name_plural = _('sale order')
|
||||
|
||||
|
||||
class SaleItem(models.Model):
|
||||
"""
|
||||
订单明细
|
||||
"""
|
||||
master = models.ForeignKey(SaleOrder)
|
||||
material = models.ForeignKey(Material,verbose_name=_("material"),limit_choices_to={"is_virtual":"0",'can_sale':'1'},blank=True,null=True)
|
||||
measure = models.ForeignKey(Measure,verbose_name=_("measure"),blank=True,null=True)
|
||||
cnt = models.DecimalField(_("count"),max_digits=14,decimal_places=4)
|
||||
stock_price = models.DecimalField(_("stock price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
sale_price = models.DecimalField(_("sale price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
discount_price = models.DecimalField(_("discount price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
tax = models.CharField(_("tax rate"),max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S052'),default='0.00')
|
||||
create_time = models.DateTimeField(_("create time"),auto_now_add=True)
|
||||
status = models.BooleanField(_("executed"),default=0)
|
||||
event_time = models.DateTimeField(_("event time"),blank=True,null=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if self.material and self.material.measure.count() > 0:
|
||||
self.measure = self.material.measure.all()[0]
|
||||
if self.material.sale_price or self.material.stock_price:
|
||||
self.sale_price = self.material.sale_price or self.material.stock_price*decimal.Decimal(1.17)/decimal.Decimal(0.6)
|
||||
super(SaleItem,self).save(force_insert,force_update,using,update_fields)
|
||||
sql = 'update sale_saleorder set amount = (select sum(sale_price*cnt) from sale_saleitem where master_id=%s) where id=%s'
|
||||
params = [self.master.id,self.master.id]
|
||||
# print sql % (self.master.id,self.master.id)
|
||||
generic.update(sql,params)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('order detail')
|
||||
verbose_name_plural = _('order detail')
|
||||
|
||||
|
||||
class PaymentCollection(generic.BO):
|
||||
"""
|
||||
销售回款
|
||||
"""
|
||||
index_weight = 3
|
||||
collection_date = models.DateField(_("collection date"),blank=True,null=True,default=datetime.date.today)
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
code = models.CharField(_("collection code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
so = models.ForeignKey(SaleOrder,verbose_name=_("sale order"))
|
||||
partner = models.ForeignKey(Partner,verbose_name=_("partner"),blank=True,null=True)
|
||||
order_amount = models.DecimalField(_("order amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
collection_amount = models.DecimalField(_("collection amount"),max_digits=14,decimal_places=4)
|
||||
bank = models.ForeignKey(BankAccount,verbose_name=_("bank account"),blank=True,null=True)
|
||||
memo = models.TextField(_("memo"),blank=True,null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s %s'%(self.code,self.so)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if self.so:
|
||||
self.partner = self.so.partner
|
||||
self.order_amount = self.so.amount
|
||||
if self.so.discount_amount > 0:
|
||||
self.order_amount -= self.so.discount_amount
|
||||
super(PaymentCollection,self).save(force_insert,force_update,using,update_fields)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Payment Collection')
|
||||
verbose_name_plural = _('Payment Collection')
|
||||
|
||||
|
||||
class OfferSheet(generic.BO):
|
||||
"""
|
||||
报价单
|
||||
"""
|
||||
index_weight = 1
|
||||
STATUS = (
|
||||
('0', _("NEW")),
|
||||
('1', _("IN PROGRESS")),
|
||||
('4', _("DROP")),
|
||||
('9', _("APPROVED")),
|
||||
)
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
partner = models.ForeignKey(Partner,verbose_name=_("partner"),limit_choices_to={"partner_type":"C"})
|
||||
offer_date = models.DateField(_("offer date"))
|
||||
deliver_date = models.DateField(_("deliver date"))
|
||||
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||
description = models.TextField(_("memo"),blank=True,null=True)
|
||||
|
||||
amount = models.DecimalField(_("money amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||
discount_amount = models.DecimalField(_("discount amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||
user = models.ForeignKey(User,verbose_name=_("offer man"),blank=True,null=True)
|
||||
|
||||
attach = models.FileField(_('offer sheet file'),blank=True,null=True,upload_to='offer sheet',help_text=u'您可导入报价明细,模板请参考文档FD0006')
|
||||
status = models.BooleanField(_("executed"),default=0)
|
||||
event_time = models.DateTimeField(_("event time"),blank=True,null=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
import decimal
|
||||
super(OfferSheet,self).save(force_insert,force_update,using,update_fields)
|
||||
if self.discount_amount > 0:
|
||||
sql = 'UPDATE sale_offeritem a SET a.discount_price = a.sale_price - ' \
|
||||
'((SELECT discount_amount/amount FROM sale_offersheet WHERE id = %s) * (a.sale_price*a.cnt)/a.cnt) WHERE a.master_id = %s'
|
||||
params = [self.id,self.id]
|
||||
# print sql % (self.id,self.id)
|
||||
generic.update(sql,params)
|
||||
|
||||
item_count = OfferItem.objects.filter(master=self).count()
|
||||
if self.attach and item_count == 0:
|
||||
path = os.path.join(settings.MEDIA_ROOT,self.attach.name)
|
||||
workbook = xlrd.open_workbook(path)
|
||||
sheet = workbook.sheet_by_index(0)
|
||||
row_count = sheet.nrows
|
||||
with transaction.atomic():
|
||||
total_amount = decimal.Decimal(0)
|
||||
for row_index in range(row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
if row_index == 0:
|
||||
doc_type = row[1]
|
||||
if doc_type.startswith('0'):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
elif row_index < 3:
|
||||
continue
|
||||
|
||||
material = None
|
||||
measure = None
|
||||
|
||||
try:
|
||||
measure = Measure.objects.get(code=row[4])
|
||||
except Exception,e:
|
||||
measure = Measure.objects.create(code=row[4],name=force_text(row[5]))
|
||||
|
||||
try:
|
||||
material = Material.objects.get(code=row[0])
|
||||
except Exception,e:
|
||||
material = Material(code=row[0],name=force_text(row[1]),spec=force_text(row[2]))
|
||||
material.sale_price = row[6]
|
||||
material.purchase_price = row[7]
|
||||
material.save()
|
||||
OfferItem.objects.create(master=self,material=material,measure=measure,cnt=row[8],brand=force_text(row[3]),
|
||||
cost_price=row[7],sale_price=row[6])
|
||||
total_amount += decimal.Decimal(row[6])*decimal.Decimal(row[8])
|
||||
sql = 'update sale_offersheet set amount = %s where id=%s'
|
||||
params = [total_amount,self.id]
|
||||
generic.update(sql,params)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('offer sheet')
|
||||
verbose_name_plural = _('offer sheet')
|
||||
|
||||
|
||||
class OfferItem(models.Model):
|
||||
"""
|
||||
订单明细
|
||||
"""
|
||||
master = models.ForeignKey(OfferSheet)
|
||||
material = models.ForeignKey(Material,verbose_name=_("material"),limit_choices_to={"is_virtual":"0",'can_sale':'1'},blank=True,null=True)
|
||||
measure = models.ForeignKey(Measure,verbose_name=_("measure"),blank=True,null=True)
|
||||
cnt = models.DecimalField(_("count"),max_digits=14,decimal_places=4)
|
||||
brand = models.CharField(_('brand'),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
cost_price = models.DecimalField(_("cost price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
stock_price = models.DecimalField(_("stock price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
sale_price = models.DecimalField(_("sale price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
discount_price = models.DecimalField(_("discount price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||
tax = models.CharField(_("tax rate"),max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S052'),default='0.00')
|
||||
create_time = models.DateTimeField(_("create time"),auto_now_add=True)
|
||||
status = models.BooleanField(_("executed"),default=0)
|
||||
event_time = models.DateTimeField(_("event time"),blank=True,null=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if self.material and self.material.measure.count() > 0:
|
||||
self.measure = self.material.measure.all()[0]
|
||||
if self.material.sale_price or self.material.stock_price:
|
||||
self.sale_price = self.material.sale_price or self.material.stock_price*decimal.Decimal(1.17)/decimal.Decimal(0.6)
|
||||
super(OfferItem,self).save(force_insert,force_update,using,update_fields)
|
||||
sql = 'update sale_offersheet set amount = (select sum(sale_price*cnt) from sale_offeritem where master_id=%s) where id=%s'
|
||||
params = [self.master.id,self.master.id]
|
||||
# print sql % (self.master.id,self.master.id)
|
||||
generic.update(sql,params)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('offer detail')
|
||||
verbose_name_plural = _('offer detail')
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -0,0 +1 @@
|
||||
default_app_config = "selfhelp.apps.MyAppConfig"
|
||||
@@ -0,0 +1,219 @@
|
||||
# coding=utf-8
|
||||
# coding = utf-8
|
||||
import datetime
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.db.models.aggregates import Sum
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import generic
|
||||
from common import const
|
||||
from basedata.models import Material,ExtraParam,Employee,Position
|
||||
from selfhelp.models import WorkOrder,WOExtraValue,WOItem,Reimbursement,ReimbursementItem,Loan,Enroll,Feedback,Activity
|
||||
|
||||
|
||||
class ParamValueInline(admin.TabularInline):
|
||||
model = WOExtraValue
|
||||
fields = ('param_name','param_value')
|
||||
readonly_fields = ['param_name']
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||
|
||||
if db_field.name == 'param_name':
|
||||
app_info = generic.get_app_model_info_from_request(request)
|
||||
instance = app_info['obj']
|
||||
if instance:
|
||||
kwargs['queryset'] = ExtraParam.objects.filter(material=instance.service)
|
||||
|
||||
return super(ParamValueInline,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
class ItemInline(admin.TabularInline):
|
||||
model = WOItem
|
||||
raw_id_fields = ['material']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
class WorkOrderAdmin(generic.BOAdmin):
|
||||
"""
|
||||
|
||||
"""
|
||||
CODE_PREFIX = 'WO'
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
list_display = ['code','begin','title','classification','business_domain','status']
|
||||
list_display_links = ['code','title']
|
||||
exclude = ['creator','modifier','creation','modification']
|
||||
search_fields = ['code','title']
|
||||
list_filter = ['classification','service','status']
|
||||
fields = (
|
||||
('begin','end',),
|
||||
('code','refer',),('classification','business_domain',),
|
||||
('service','project',),
|
||||
('title','status',),('description',),('attach',),('detail',)
|
||||
)
|
||||
readonly_fields = ['status']
|
||||
raw_id_fields = ['service','project','refer']
|
||||
inlines = [ItemInline,ParamValueInline]
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj.user is None:
|
||||
obj.user = request.user
|
||||
super(WorkOrderAdmin,self).save_model(request,obj,form,change)
|
||||
|
||||
def get_changeform_initial_data(self, request):
|
||||
import datetime
|
||||
td = datetime.date.today()
|
||||
end = td + datetime.timedelta(30)
|
||||
return {'begin':datetime.date.today, 'end':end}
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||
if db_field.name == 'refer':
|
||||
app_info = generic.get_app_model_info_from_request(request)
|
||||
if app_info and app_info['obj']:
|
||||
kwargs['queryset'] = WorkOrder.objects.exclude(id=app_info['id'])
|
||||
|
||||
return super(WorkOrderAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||
|
||||
|
||||
class LoanAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'JK'
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
list_display = ['code','title','project','loan_amount','applier','status']
|
||||
list_display_links = ['code','title']
|
||||
readonly_fields = ['status','logout_time','logout_amount']
|
||||
raw_id_fields = ['project','user']
|
||||
fields = (
|
||||
('code',),('title','loan_amount',),('description',),('project'),('user','status'),('logout_time','logout_amount',),
|
||||
)
|
||||
extra_buttons = [{'href':'pay','title':_('pay')}]
|
||||
search_fields = ['code','title','user__username']
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
if object_id:
|
||||
try:
|
||||
obj = Loan.objects.get(id=object_id)
|
||||
if obj and obj.status == 'P':
|
||||
extra_context = extra_context or {}
|
||||
extra_context.update(dict(readonly=True))
|
||||
except Exception,e:
|
||||
pass
|
||||
return super(LoanAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj and obj.user is None:
|
||||
obj.user = request.user
|
||||
super(LoanAdmin,self).save_model(request,obj,form,change)
|
||||
|
||||
|
||||
class ReimbursementItemInline(admin.TabularInline):
|
||||
model = ReimbursementItem
|
||||
raw_id_fields = ['expense_account']
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
if obj:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
class ReimbursementAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'BX'
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
list_display = ['code','title','project','amount','applier','status']
|
||||
list_display_links = ['code','title']
|
||||
inlines = [ReimbursementItemInline]
|
||||
raw_id_fields = ['project','wo','user','org']
|
||||
readonly_fields = ['loan_amount','pay_time','status','amount']
|
||||
fieldsets = [
|
||||
(None,{'fields':[('code','user'),('title','amount','status',),('description',),('project','wo',)]}),
|
||||
(_('fico'),{'fields':[('org',),('loan',),('logout_amount','pay_amount',)],'classes': ['collapse']})
|
||||
]
|
||||
extra_buttons = [{'href':'pay','title':_('pay')}]
|
||||
search_fields = ['code','title','project__code','project__name','user__username']
|
||||
date_hierarchy = 'begin'
|
||||
|
||||
def get_changeform_initial_data(self, request):
|
||||
apps = generic.get_app_model_info_from_request(request)
|
||||
obj = getattr(apps,'obj',None)
|
||||
current = request.user
|
||||
if obj:
|
||||
current = obj.user
|
||||
sm = Loan.objects.filter(user=current).aggregate(Sum('loan_amount')).get('loan_amount__sum') or 0.00
|
||||
return {'loan_amount':sm}
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if obj and obj.user is None:
|
||||
obj.user = request.user
|
||||
|
||||
super(ReimbursementAdmin,self).save_model(request,obj,form,change)
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||
if db_field.name == 'loan':
|
||||
apps = generic.get_app_model_info_from_request(request)
|
||||
current = request.user
|
||||
if apps:
|
||||
obj = apps.get('obj')
|
||||
current = obj.user
|
||||
if obj.status == 'P':
|
||||
kwargs['queryset']=Loan.objects.filter(id=obj.loan.id)
|
||||
else:
|
||||
kwargs['queryset']=Loan.objects.filter(user=current,is_clear=0)
|
||||
else:
|
||||
kwargs['queryset']=Loan.objects.filter(user=current,is_clear=0)
|
||||
return super(ReimbursementAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
if object_id:
|
||||
try:
|
||||
obj = Reimbursement.objects.get(id=object_id)
|
||||
if obj and obj.status == 'P':
|
||||
extra_context = extra_context or {}
|
||||
extra_context.update(dict(readonly=True))
|
||||
except Exception,e:
|
||||
pass
|
||||
return super(ReimbursementAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||
|
||||
|
||||
class EnrollInline(admin.TabularInline):
|
||||
model = Enroll
|
||||
|
||||
|
||||
class FeedbackInline(admin.TabularInline):
|
||||
model = Feedback
|
||||
|
||||
|
||||
class ActivityAdmin(generic.BOAdmin):
|
||||
CODE_PREFIX = 'AC'
|
||||
CODE_NUMBER_WIDTH = 5
|
||||
list_display = ['code','begin_time','end_time','title','classification','room']
|
||||
list_display_links = ['code','title']
|
||||
raw_id_fields = ['room','parent']
|
||||
fieldsets = [
|
||||
(None,{'fields':[('begin_time','end_time',),('title','classification',),('description',),
|
||||
('host','speaker',),('room','location',),('attach',)]}),
|
||||
(_('other info'),{'fields':[('mail_list',),('parent',),('mail_notice','short_message_notice','weixin_notice',)],'classes': ['collapse']})
|
||||
]
|
||||
|
||||
def get_changeform_initial_data(self, request):
|
||||
now = datetime.datetime.now()
|
||||
begin = now + datetime.timedelta(hours=12)
|
||||
end = begin + datetime.timedelta(hours=6)
|
||||
return {'begin_time':begin,'end_time':end}
|
||||
|
||||
admin.site.register(WorkOrder,WorkOrderAdmin)
|
||||
admin.site.register(Loan,LoanAdmin)
|
||||
admin.site.register(Reimbursement,ReimbursementAdmin)
|
||||
admin.site.register(Activity,ActivityAdmin)
|
||||
@@ -0,0 +1,11 @@
|
||||
# created at 15-5-23
|
||||
# coding=utf-8
|
||||
__author__ = 'zhugl'
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class MyAppConfig(AppConfig):
|
||||
name = 'selfhelp'
|
||||
verbose_name = _("self help")
|
||||
@@ -0,0 +1,330 @@
|
||||
# coding=utf-8
|
||||
import datetime
|
||||
import os
|
||||
import xlrd
|
||||
import decimal
|
||||
from django.db import transaction
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.text import force_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import generic
|
||||
from common import const
|
||||
from mis import settings
|
||||
from basedata.models import Material,ExtraParam,Project,ExpenseAccount,Measure
|
||||
from organ.models import OrgUnit
|
||||
|
||||
|
||||
class WorkOrder(generic.BO):
|
||||
"""
|
||||
|
||||
"""
|
||||
index_weight = 1
|
||||
code = models.CharField(_("workorder code"),blank=True,null=True,max_length=const.DB_CHAR_CODE_10)
|
||||
refer = models.ForeignKey("self",verbose_name=_("refer wo"),blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_120)
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
business_domain = models.CharField(_("business domain"),max_length=const.DB_CHAR_CODE_4,choices=const.get_value_list('S045'),default='OT')
|
||||
classification = models.CharField(_("classification"),max_length=const.DB_CHAR_CODE_4,choices=const.get_value_list('S044'),default='D')
|
||||
service = models.ForeignKey(Material,verbose_name=_("service name"),null=True,blank=True,limit_choices_to={"is_virtual":"1"})
|
||||
project = models.ForeignKey(Project,verbose_name=_("project"),null=True,blank=True)
|
||||
status = models.CharField(_("status"),blank=True,null=True,default='NEW',max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S046'))
|
||||
answer = models.TextField(_("answer"),blank=True,null=True)
|
||||
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||
attach = models.FileField(_('attach'),blank=True,null=True,help_text=u'工单附件,不导入明细。')
|
||||
detail = models.FileField(_('to be imported detail'),blank=True,null=True,help_text=u'您可导入需求明细,模板请参考文档FD0007')
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s-%s" % (self.code,self.title)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
super(WorkOrder,self).save(force_insert,force_update,using,update_fields)
|
||||
if self.service:
|
||||
material = self.service
|
||||
if self.woextravalue_set.count() < 1 and material.extraparam_set and material.extraparam_set.count() > 0:
|
||||
for param in material.extraparam_set.all():
|
||||
extra_param = WOExtraValue.objects.create(workorder=self,param_name=param)
|
||||
self.woextravalue_set.add(extra_param)
|
||||
item_count = WOItem.objects.filter(workorder=self).count()
|
||||
if self.detail and item_count == 0:
|
||||
path = os.path.join(settings.MEDIA_ROOT,self.detail.name)
|
||||
workbook = xlrd.open_workbook(path)
|
||||
sheet = workbook.sheet_by_index(0)
|
||||
row_count = sheet.nrows
|
||||
with transaction.atomic():
|
||||
for row_index in range(row_count):
|
||||
row = sheet.row_values(row_index)
|
||||
if row_index == 0:
|
||||
doc_type = row[1]
|
||||
if doc_type.startswith('0'):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
elif row_index < 3:
|
||||
continue
|
||||
material = None
|
||||
measure = None
|
||||
try:
|
||||
measure = Measure.objects.get(code=row[4])
|
||||
except Exception,e:
|
||||
measure = Measure.objects.create(code=row[4],name=force_text(row[5]))
|
||||
try:
|
||||
material = Material.objects.get(code=row[0])
|
||||
except Exception,e:
|
||||
material = Material(code=row[0],name=force_text(row[1]),spec=force_text(row[2]))
|
||||
material.save()
|
||||
WOItem.objects.create(workorder=self,material=material,measure=measure,amount=row[6])
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("workorder apply")
|
||||
verbose_name_plural = _("workorder apply")
|
||||
|
||||
class Media:
|
||||
js = ('js/workorder.js',)
|
||||
|
||||
|
||||
class WOExtraValue(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
workorder = models.ForeignKey(WorkOrder,verbose_name=_("workorder"))
|
||||
param_name = models.ForeignKey(ExtraParam,verbose_name=_("extra param"))
|
||||
param_value = models.CharField(_("param value"),blank=True,null=True,max_length=const.DB_CHAR_NAME_40)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("extra value")
|
||||
verbose_name_plural = _("extra values")
|
||||
|
||||
|
||||
class WOItem(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
workorder = models.ForeignKey(WorkOrder,verbose_name=_("workorder"))
|
||||
material = models.ForeignKey(Material,verbose_name=_("material"),null=True,blank=True,limit_choices_to={"is_virtual":"0"})
|
||||
amount = models.DecimalField(_("amount"),max_digits=10,decimal_places=4,blank=True,null=True)
|
||||
measure = models.ForeignKey(Measure,verbose_name=_('measure'),blank=True,null=True)
|
||||
price = models.DecimalField(_("price"),max_digits=10,decimal_places=4,blank=True,null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("workorder item")
|
||||
verbose_name_plural = _("workorder items")
|
||||
|
||||
|
||||
class Loan(generic.BO):
|
||||
"""
|
||||
|
||||
"""
|
||||
LOAD_STATUS = (
|
||||
('N',_("NEW")),
|
||||
('I',_("IN PROGRESS")),
|
||||
('A',_("APPROVED")),
|
||||
('P',_("PAYED"))
|
||||
)
|
||||
index_weight = 3
|
||||
code = models.CharField(_("loan code"),max_length=const.DB_CHAR_CODE_10,blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_120)
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
project = models.ForeignKey(Project,verbose_name=_("project"))
|
||||
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||
status = models.CharField(_("status"),blank=True,null=True,default='N',max_length=const.DB_CHAR_CODE_2,choices=LOAD_STATUS)
|
||||
logout_time = models.DateTimeField(_("logout time"),blank=True,null=True)
|
||||
loan_amount = models.DecimalField(_("loan amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
logout_amount = models.DecimalField(_("logout amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
pay_user = models.CharField(_('pay user'),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
pay_time = models.DateTimeField(_('pay time'),blank=True,null=True)
|
||||
is_clear = models.BooleanField(_('is clear'),default=False)
|
||||
|
||||
def __unicode__(self):
|
||||
import decimal
|
||||
left = self.loan_amount
|
||||
left -= self.logout_amount or decimal.Decimal(0.00)
|
||||
name = '%s%s'%(self.user.last_name,self.user.first_name)
|
||||
return '%s %s %s J:%.2f Y:%.2f' % (self.code,name,self.title,self.loan_amount,left)
|
||||
|
||||
def applier(self):
|
||||
return u'%s%s'%(self.user.last_name,self.user.first_name)
|
||||
|
||||
applier.short_description = _('applier')
|
||||
|
||||
def action_pay(self,request):
|
||||
self.pay_time = datetime.datetime.now()
|
||||
self.pay_user = request.user.username
|
||||
self.status = 'P'
|
||||
self.save()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("loan")
|
||||
verbose_name_plural = _("loans")
|
||||
permissions = (
|
||||
('financial_pay',_("financial pay")),
|
||||
)
|
||||
|
||||
|
||||
class Reimbursement(generic.BO):
|
||||
"""
|
||||
|
||||
"""
|
||||
REIM_STATUS = (
|
||||
('N',_("NEW")),
|
||||
('I',_("IN PROGRESS")),
|
||||
('A',_("APPROVED")),
|
||||
('P',_("PAYED"))
|
||||
)
|
||||
index_weight = 2
|
||||
code = models.CharField(_("fee code"),max_length=const.DB_CHAR_CODE_10,blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_120)
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
project = models.ForeignKey(Project,verbose_name=_("project"))
|
||||
wo = models.ForeignKey(WorkOrder,verbose_name=_("work order"),null=True,blank=True)
|
||||
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||
org = models.ForeignKey(OrgUnit,verbose_name=_("cost center"),blank=True,null=True)
|
||||
bank_account = models.CharField(_("bank account"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||
status = models.CharField(_("status"),blank=True,null=True,default='N',max_length=const.DB_CHAR_CODE_2,choices=REIM_STATUS)
|
||||
amount = models.DecimalField(_("amount of money"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
loan = models.ForeignKey(Loan,verbose_name=_("loan record"),blank=True,null=True)
|
||||
loan_amount = models.DecimalField(_("loan amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
logout_amount = models.DecimalField(_("logout amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
pay_amount = models.DecimalField(_("pay amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||
pay_time = models.DateTimeField(_("pay time"),blank=True,null=True)
|
||||
pay_user = models.CharField(_('pay user'),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
|
||||
def applier(self):
|
||||
return u'%s%s'%(self.user.last_name,self.user.first_name)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
import decimal
|
||||
if self.loan:
|
||||
left = self.loan.loan_amount
|
||||
left -= self.loan.logout_amount or decimal.Decimal(0.00)
|
||||
if self.logout_amount is None or self.logout_amount == '':
|
||||
if self.amount < left:
|
||||
self.logout_amount = self.amount
|
||||
else:
|
||||
self.logout_amount = left
|
||||
self.pay_amount = self.amount
|
||||
if self.logout_amount:
|
||||
self.pay_amount -= self.logout_amount
|
||||
super(Reimbursement,self).save(force_insert,force_update,using,update_fields)
|
||||
|
||||
def action_pay(self,request):
|
||||
if self.loan:
|
||||
if self.logout_amount is None or self.logout_amount == '':
|
||||
self.logout_amount = self.amount
|
||||
if self.logout_amount < 0:
|
||||
raise Exception(u'核销金额小于0')
|
||||
|
||||
if self.loan.logout_amount is None:
|
||||
self.loan.logout_amount = self.logout_amount
|
||||
else:
|
||||
self.loan.logout_amount += self.logout_amount
|
||||
|
||||
if self.loan.loan_amount == self.loan.logout_amount:
|
||||
self.loan.is_clear=True
|
||||
|
||||
self.loan.logout_time = datetime.datetime.now()
|
||||
self.loan.save()
|
||||
|
||||
if self.amount > self.logout_amount:
|
||||
self.pay_amount = self.amount - self.logout_amount
|
||||
|
||||
self.pay_time = datetime.datetime.now()
|
||||
self.pay_user = request.user.username
|
||||
self.status = 'P'
|
||||
self.save()
|
||||
|
||||
applier.short_description = _('applier')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("reimbursement")
|
||||
verbose_name_plural = _("reimbursements")
|
||||
permissions = (
|
||||
('financial_pay',_("financial pay")),
|
||||
)
|
||||
|
||||
|
||||
class ReimbursementItem(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
import datetime
|
||||
reimbursement = models.ForeignKey(Reimbursement,verbose_name=_("reimbursement"))
|
||||
expense_account = models.ForeignKey(ExpenseAccount,verbose_name=_("expenses account"))
|
||||
begin = models.DateField(_("occur date"),default=datetime.date.today)
|
||||
amount = models.DecimalField(_("amount of money"),max_digits=10,decimal_places=2)
|
||||
memo = models.CharField(_("memo"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
super(ReimbursementItem,self).save(force_insert,force_update,using,update_fields)
|
||||
sql = 'UPDATE selfhelp_reimbursement SET amount = (SELECT SUM(amount) FROM selfhelp_reimbursementitem WHERE reimbursement_id = %s) WHERE id = %s'
|
||||
params = [self.reimbursement.id,self.reimbursement.id]
|
||||
generic.update(sql,params)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("fee item")
|
||||
verbose_name_plural = _("fee items")
|
||||
|
||||
|
||||
class Activity(generic.BO):
|
||||
"""
|
||||
|
||||
"""
|
||||
CLASSIFICATION = (
|
||||
('T',_("Train")),
|
||||
('M',_("Meeting")),
|
||||
('G',_("Community")),
|
||||
)
|
||||
index_index_weight = 4
|
||||
begin_time = models.DateTimeField(_('begin time'))
|
||||
end_time = models.DateTimeField(_('end time'))
|
||||
enroll_deadline = models.DateTimeField(_('enroll deadline'),blank=True,null=True)
|
||||
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_120)
|
||||
parent = models.ForeignKey('self',verbose_name=_("parent"),blank=True,null=True)
|
||||
description = models.TextField(_("description"),blank=True,null=True)
|
||||
host = models.CharField(_("host"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
speaker = models.CharField(_("speaker"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
accept_enroll = models.BooleanField(_("accept enroll"),default=1)
|
||||
room = models.ForeignKey(Material,verbose_name=_("room"),blank=True,null=True,limit_choices_to={'tp':20})
|
||||
location = models.CharField(_("location"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
classification = models.CharField(_("classification"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=CLASSIFICATION,default='M')
|
||||
mail_list = models.TextField(_("mail list"),blank=True,null=True)
|
||||
mail_notice = models.BooleanField(_("mail notice"),default=1)
|
||||
short_message_notice = models.BooleanField(_("short message notice"),default=1)
|
||||
weixin_notice = models.BooleanField(_("weixin notice"),default=1)
|
||||
status = models.BooleanField(_("published"),default=0)
|
||||
publish_time = models.DateTimeField(_("publish time"),blank=True,null=True)
|
||||
attach = models.FileField(_("attach"),blank=True,null=True,upload_to='activity')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("activity")
|
||||
verbose_name_plural = _("activities")
|
||||
|
||||
|
||||
class Feedback(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
RANK = (
|
||||
('A','A'),
|
||||
('B','B'),
|
||||
('C','C'),
|
||||
('D','D'),
|
||||
)
|
||||
activity = models.ForeignKey(Activity)
|
||||
user = models.ForeignKey(User)
|
||||
feed_time = models.DateTimeField(_("feedback time"),auto_now_add=True)
|
||||
rank = models.CharField(_('rank'),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=RANK,default='B')
|
||||
comment = models.CharField(_("suggest"),blank=True,null=True,max_length=const.DB_CHAR_NAME_80)
|
||||
|
||||
|
||||
class Enroll(models.Model):
|
||||
"""
|
||||
|
||||
"""
|
||||
activity = models.ForeignKey(Activity)
|
||||
user = models.ForeignKey(User)
|
||||
enroll_time = models.DateTimeField(_("enroll time"),auto_now_add=True)
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.conf.urls import include, url,static
|
||||
import selfhelp.views
|
||||
|
||||
urlpatterns = [
|
||||
url(r"(?P<model>\w+)/(?P<object_id>\d+)/pay", selfhelp.views.pay_action),
|
||||
]
|
||||
@@ -0,0 +1,46 @@
|
||||
# coding=utf-8
|
||||
from django.contrib.admin import site
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import connection
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.utils.encoding import force_text
|
||||
from django.template.response import TemplateResponse
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
def pay_action(request,model,object_id):
|
||||
title = _("Are you sure?")
|
||||
ct = ContentType.objects.get(app_label='selfhelp',model=model)
|
||||
obj = ct.get_object_for_this_type(id=int(object_id))
|
||||
opts = obj._meta
|
||||
objects_name = force_text(opts.verbose_name)
|
||||
|
||||
if model == 'reimbursement':
|
||||
loan = obj.loan
|
||||
amount = obj.logout_amount
|
||||
if loan and (amount is None or amount< 0):
|
||||
messages.error(request,u'您选择了借款单据,但是未正确填写核销金额,请在\'财务信息\'栏目中更正')
|
||||
return HttpResponseRedirect("/admin/selfhelp/%s/%s"%(model,object_id))
|
||||
|
||||
if request.POST.get("post"):
|
||||
try:
|
||||
obj.action_pay(request)
|
||||
messages.success(request,_('action successfully'))
|
||||
except Exception,e:
|
||||
messages.error(request,e)
|
||||
|
||||
return HttpResponseRedirect("/admin/selfhelp/%s/%s"%(model,object_id))
|
||||
|
||||
context = dict(
|
||||
site.each_context(request),
|
||||
title=title,
|
||||
opts=opts,
|
||||
objects_name=objects_name,
|
||||
object=obj,
|
||||
action_name=_('pay')
|
||||
)
|
||||
request.current_app = site.name
|
||||
|
||||
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
table#workflow-history{
|
||||
width:100%
|
||||
}
|
||||
table#workflow-history tbody th,table#workflow-history tbody td.col {
|
||||
width: 16em;
|
||||
}
|
||||
|
||||
.submitlink {
|
||||
padding-left: 12px;
|
||||
background: url(../img/icon-yes.gif) 0 .25em no-repeat;
|
||||
}
|
||||
|
||||
a.submitlink:link, a.submitlink:visited {
|
||||
color: #CC3434;
|
||||
}
|
||||
|
||||
a.submitlink:hover {
|
||||
color: #993333;
|
||||
}
|
||||
|
||||
label.control{
|
||||
width:4em;
|
||||
}
|
||||
|
||||
p.next-node{
|
||||
font-weight:bold;
|
||||
}
|
||||
ul.node-users{
|
||||
list-style:none;
|
||||
}
|
||||
p.tooltip{
|
||||
color:#999;
|
||||
}
|
||||
li.done-link{
|
||||
padding-left: 12px;
|
||||
background: url(../img/icon_yes.gif) 0 .2em no-repeat;
|
||||
}
|
||||
@@ -0,0 +1,984 @@
|
||||
/* 智捷ERP SmartJet ERP - 1:1 High-Fidelity UI System v4.0 */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
|
||||
|
||||
:root {
|
||||
--primary: #6366f1;
|
||||
--primary-light: #818cf8;
|
||||
--primary-bg: #f5f3ff;
|
||||
|
||||
--slate-50: #f8fafc;
|
||||
--slate-100: #f1f5f9;
|
||||
--slate-200: #e2e8f0;
|
||||
--slate-300: #cbd5e1;
|
||||
--slate-400: #94a3b8;
|
||||
--slate-500: #64748b;
|
||||
--slate-600: #475569;
|
||||
--slate-700: #334155;
|
||||
--slate-800: #1e293b;
|
||||
--slate-900: #0f172a;
|
||||
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--info: #3b82f6;
|
||||
|
||||
--sidebar-width: 240px;
|
||||
--sidebar-collapsed-width: 0px;
|
||||
--topbar-height: 64px;
|
||||
--radius-lg: 16px;
|
||||
--radius-md: 10px;
|
||||
--shadow-card: 0 10px 15px -3px rgba(0, 0, 0, 0.04), 0 4px 6px -2px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f1f5f9 !important;
|
||||
color: var(--slate-800) !important;
|
||||
font-family: 'Plus Jakarta Sans', -apple-system, sans-serif !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: flex !important;
|
||||
min-height: 100vh;
|
||||
min-width: 0 !important;
|
||||
max-width: 100vw;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* --- SIDEBAR --- */
|
||||
#header {
|
||||
width: var(--sidebar-width) !important;
|
||||
height: 100vh !important;
|
||||
position: fixed !important;
|
||||
left: 0; top: 0;
|
||||
background: #ffffff !important;
|
||||
border-right: 1px solid var(--slate-200) !important;
|
||||
padding: 0 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
z-index: 1000 !important;
|
||||
color: var(--slate-800) !important;
|
||||
line-height: normal !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* 覆盖 Django admin 默认深色顶栏链接样式(白字导致侧栏文字不可见) */
|
||||
#header a:link,
|
||||
#header a:visited {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#header a:focus,
|
||||
#header a:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.brand-section {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: linear-gradient(135deg, #6366f1, #a855f7);
|
||||
border-radius: 8px;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.brand-text h1 {
|
||||
font-size: 18px !important;
|
||||
font-weight: 800 !important;
|
||||
color: #1e293b !important;
|
||||
margin: 0 !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
.brand-text span {
|
||||
font-size: 11px;
|
||||
color: var(--slate-400);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
padding: 0 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nav-group-label {
|
||||
padding: 20px 12px 10px 12px;
|
||||
color: var(--slate-400);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
color: var(--slate-600) !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-item:hover,
|
||||
.nav-item:focus {
|
||||
background: var(--slate-50);
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-item.active,
|
||||
.nav-item.active:link,
|
||||
.nav-item.active:visited {
|
||||
background: var(--primary-bg) !important;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-item i { margin-right: 12px; font-size: 18px; width: 20px; text-align: center; }
|
||||
.nav-item .arrow { margin-left: auto; font-size: 10px; color: var(--slate-300); }
|
||||
|
||||
.sidebar-footer {
|
||||
padding: 16px;
|
||||
border-top: 1px solid var(--slate-100);
|
||||
position: relative;
|
||||
z-index: 1002;
|
||||
flex-shrink: 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.nav-item-btn {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button.nav-item-btn:hover,
|
||||
button.nav-item-btn:focus {
|
||||
background: var(--slate-50);
|
||||
color: var(--primary) !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 侧栏收起 */
|
||||
#header {
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #header {
|
||||
transform: translateX(-100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #main {
|
||||
margin-left: 0 !important;
|
||||
max-width: 100vw !important;
|
||||
}
|
||||
|
||||
/* --- TOPBAR --- */
|
||||
#main {
|
||||
flex: 1 1 auto !important;
|
||||
margin-left: var(--sidebar-width) !important;
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
max-width: calc(100vw - var(--sidebar-width)) !important;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
height: var(--topbar-height);
|
||||
background: #ffffff !important;
|
||||
opacity: 1 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 32px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 900;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.02);
|
||||
}
|
||||
|
||||
.topbar-left { display: flex; align-items: center; flex: 1; }
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
background: #ffffff !important;
|
||||
opacity: 1 !important;
|
||||
position: relative;
|
||||
z-index: 901;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.breadcrumb-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--slate-400);
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.breadcrumb-new span { color: var(--slate-800); font-weight: 600; }
|
||||
|
||||
.search-btn, .icon-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--slate-500);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.search-btn:hover, .icon-btn:hover,
|
||||
.search-btn:focus, .icon-btn:focus {
|
||||
background: var(--slate-100);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.user-menu-wrap {
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
border-left: 1px solid var(--slate-200);
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 4px 8px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: #ffffff !important;
|
||||
opacity: 1 !important;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.user-profile:hover,
|
||||
.user-profile:focus {
|
||||
background: var(--slate-50) !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.user-caret {
|
||||
font-size: 10px;
|
||||
color: var(--slate-400);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
min-width: 140px;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--slate-200);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
padding: 6px;
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
.user-dropdown.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.user-dropdown a {
|
||||
display: block;
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
color: var(--slate-700) !important;
|
||||
text-decoration: none !important;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.user-dropdown a:hover {
|
||||
background: var(--slate-50);
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
background: var(--slate-200);
|
||||
flex-shrink: 0;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.user-info { display: flex; flex-direction: column; background: transparent; }
|
||||
.user-name { font-weight: 700; font-size: 13px; color: var(--slate-800) !important; opacity: 1 !important; }
|
||||
.user-role { font-size: 11px; color: var(--slate-500) !important; opacity: 1 !important; }
|
||||
|
||||
/* --- CONTENT --- */
|
||||
#content {
|
||||
padding: 32px !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dashboard #content {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
#content-main {
|
||||
float: none !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.dashboard #content > h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#content > h1 {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--slate-900);
|
||||
margin: 0 0 24px;
|
||||
padding-right: 120px;
|
||||
}
|
||||
|
||||
.colM, .colMS {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#changelist, #changelist-form, .results {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* --- STATS CARDS --- */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #ffffff;
|
||||
padding: 24px;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-header { display: flex; justify-content: space-between; margin-bottom: 12px; }
|
||||
.stat-icon-bg { width: 40px; height: 40px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-size: 18px; }
|
||||
.stat-info-icon { color: var(--slate-300); font-size: 14px; cursor: help; }
|
||||
|
||||
.stat-value { font-size: 24px; font-weight: 800; color: var(--slate-900); margin-bottom: 8px; }
|
||||
.stat-label { font-size: 14px; color: var(--slate-500); font-weight: 500; }
|
||||
|
||||
.stat-footer { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; border-top: 1px solid var(--slate-50); padding-top: 16px; }
|
||||
.stat-trend { font-size: 12px; font-weight: 600; display: flex; align-items: center; gap: 4px; }
|
||||
.trend-up { color: var(--success); }
|
||||
.trend-down { color: var(--danger); }
|
||||
|
||||
.sparkline { width: 60px; height: 30px; }
|
||||
|
||||
/* --- DATA MODULES --- */
|
||||
.dashboard-main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.module-card {
|
||||
background: white;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
padding: 24px 32px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--slate-900);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* --- TABLE STYLES --- */
|
||||
.table-container { overflow-x: auto; max-width: 100%; padding: 0 12px 12px 12px; }
|
||||
table { width: 100%; max-width: 100%; border-collapse: collapse; }
|
||||
th { text-align: left; padding: 12px 20px; font-size: 12px; color: var(--slate-400); font-weight: 600; border-bottom: 1px solid var(--slate-100); }
|
||||
td { padding: 16px 20px; font-size: 13px; color: var(--slate-700); border-bottom: 1px solid var(--slate-50); }
|
||||
tr:last-child td { border: none; }
|
||||
|
||||
.badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.badge-new { background: #eef2ff; color: #6366f1; }
|
||||
.badge-audit { background: #ecfdf5; color: #10b981; }
|
||||
.badge-update { background: #eff6ff; color: #3b82f6; }
|
||||
.badge-submit { background: #fff7ed; color: #f97316; }
|
||||
|
||||
/* --- TASK LIST --- */
|
||||
.task-list { padding: 0 24px 24px 24px; }
|
||||
.task-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--slate-50);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.task-item:hover { border-color: var(--slate-200); background: var(--slate-50); }
|
||||
|
||||
.task-icon { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 16px; color: white; }
|
||||
.task-content { flex: 1; }
|
||||
.task-name { font-size: 13px; font-weight: 700; color: var(--slate-800); }
|
||||
.task-info { font-size: 11px; color: var(--slate-400); margin-top: 2px; }
|
||||
.task-time { font-size: 11px; color: var(--slate-400); }
|
||||
|
||||
.priority-tag { font-size: 10px; font-weight: 800; padding: 2px 6px; border-radius: 4px; text-transform: uppercase; margin-top: 8px; display: inline-block; }
|
||||
.p-urgent { background: #fef2f2; color: #ef4444; }
|
||||
.p-high { background: #fff7ed; color: #f97316; }
|
||||
.p-medium { background: #fffbeb; color: #f59e0b; }
|
||||
.p-low { background: #f0fdf4; color: #10b981; }
|
||||
|
||||
.view-all { text-align: center; padding: 20px; border-top: 1px solid var(--slate-50); }
|
||||
.view-all a { color: var(--primary); text-decoration: none; font-size: 13px; font-weight: 700; }
|
||||
|
||||
/* --- ADMIN FORM ACTIONS --- */
|
||||
#content-main {
|
||||
position: relative;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.object-tools {
|
||||
float: none !important;
|
||||
position: absolute !important;
|
||||
top: 32px;
|
||||
right: 32px;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
opacity: 1 !important;
|
||||
z-index: 5;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.object-tools li {
|
||||
height: auto !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.object-tools a:link,
|
||||
.object-tools a:visited,
|
||||
.object-tools a.historylink {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
float: none !important;
|
||||
padding: 8px 16px !important;
|
||||
background: var(--slate-600) !important;
|
||||
color: #ffffff !important;
|
||||
opacity: 1 !important;
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: none !important;
|
||||
letter-spacing: 0 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.object-tools a:hover,
|
||||
.object-tools a:focus {
|
||||
background: var(--slate-700) !important;
|
||||
color: #ffffff !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.submit-row {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
overflow: visible !important;
|
||||
padding: 16px 20px !important;
|
||||
margin: 24px 0 0 !important;
|
||||
background: #ffffff !important;
|
||||
border: 1px solid var(--slate-200) !important;
|
||||
border-radius: var(--radius-md) !important;
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
.submit-row p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.submit-row p.deletelink-box {
|
||||
float: none !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
.submit-row a.deletelink {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: auto !important;
|
||||
min-height: 38px;
|
||||
line-height: 1.4 !important;
|
||||
padding: 10px 20px !important;
|
||||
border-radius: 8px !important;
|
||||
color: #ffffff !important;
|
||||
background: var(--danger) !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.submit-row a.deletelink:hover,
|
||||
.submit-row a.deletelink:focus {
|
||||
background: #dc2626 !important;
|
||||
}
|
||||
|
||||
.submit-row input,
|
||||
.submit-row a.button,
|
||||
.submit-row .button {
|
||||
height: auto !important;
|
||||
min-height: 38px;
|
||||
line-height: 1.4 !important;
|
||||
padding: 10px 20px !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: none !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.submit-row input.default {
|
||||
background: var(--primary) !important;
|
||||
border-color: var(--primary) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.submit-row input[name="_addanother"],
|
||||
.submit-row input[name="_continue"],
|
||||
.submit-row input[name="_saveasnew"] {
|
||||
background: #ffffff !important;
|
||||
color: var(--slate-700) !important;
|
||||
border: 1px solid var(--slate-300) !important;
|
||||
}
|
||||
|
||||
/* 双栏选择器 */
|
||||
.selector {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.selector .selector-available h2,
|
||||
.selector .selector-chosen h2 {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--slate-700);
|
||||
}
|
||||
|
||||
.selector select {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.selector .selector-filter label {
|
||||
font-size: 12px;
|
||||
color: var(--slate-500);
|
||||
}
|
||||
|
||||
/* 表单模块 */
|
||||
.module h2, fieldset.module h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--slate-800);
|
||||
background: var(--slate-50) !important;
|
||||
border-bottom: 1px solid var(--slate-200);
|
||||
}
|
||||
|
||||
.form-row label {
|
||||
color: var(--slate-700) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Hide messy native elements */
|
||||
#footer, .breadcrumbs, #content-related { display: none !important; }
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.dashboard-main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding: 20px !important;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- LOGIN PAGE --- */
|
||||
body.login-page {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
background: var(--slate-100);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.login-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px;
|
||||
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #a855f7 100%);
|
||||
color: #ffffff;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-brand::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at 20% 80%, rgba(255,255,255,0.12) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 20%, rgba(255,255,255,0.08) 0%, transparent 40%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-brand-inner {
|
||||
position: relative;
|
||||
max-width: 420px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login-brand-logo {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
margin-bottom: 24px;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.login-brand h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
margin: 0 0 8px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.login-brand-tagline {
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
opacity: 0.8;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.login-brand-desc {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
margin: 0 0 32px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.login-brand-features {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login-brand-features li {
|
||||
padding: 10px 0;
|
||||
padding-left: 24px;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.login-brand-features li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.login-panel {
|
||||
flex: 0 0 480px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px;
|
||||
background: var(--slate-50);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: #ffffff;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
padding: 40px 36px;
|
||||
border: 1px solid var(--slate-200);
|
||||
}
|
||||
|
||||
.login-card-header {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.login-card-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
color: var(--slate-900);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.login-card-header p {
|
||||
font-size: 14px;
|
||||
color: var(--slate-500);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login-error {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.login-field {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.login-field label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--slate-700);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-field input[type="text"],
|
||||
.login-field input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
border: 1px solid var(--slate-300);
|
||||
border-radius: var(--radius-md);
|
||||
background: #ffffff;
|
||||
color: var(--slate-800);
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.login-field input[type="text"]:focus,
|
||||
.login-field input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px var(--primary-bg);
|
||||
}
|
||||
|
||||
.login-submit {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
margin-top: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
font-family: inherit;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(135deg, var(--primary), #7c3aed);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s, transform 0.15s;
|
||||
}
|
||||
|
||||
.login-submit:hover {
|
||||
opacity: 0.92;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.login-submit:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.login-demo {
|
||||
background: var(--primary-bg);
|
||||
border: 1px solid #e0e7ff;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.login-demo-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-demo-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--slate-600);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.login-demo-row span {
|
||||
min-width: 48px;
|
||||
color: var(--slate-500);
|
||||
}
|
||||
|
||||
.login-demo-row code {
|
||||
font-family: 'Consolas', monospace;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--slate-800);
|
||||
}
|
||||
|
||||
.login-demo-divider {
|
||||
height: 1px;
|
||||
background: #e0e7ff;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.login-footer-link {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-footer-link a {
|
||||
font-size: 12px;
|
||||
color: var(--slate-400);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.login-footer-link a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.login-shell {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
flex: none;
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.login-brand-inner {
|
||||
max-width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-brand-logo {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.login-brand-features {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-panel {
|
||||
flex: 1;
|
||||
padding: 24px 16px 32px;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 122 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 299 B |
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
author:zhugl
|
||||
date:2015-05-15
|
||||
*/
|
||||
(function($) {
|
||||
$(document).ready(function() {
|
||||
/**
|
||||
set operation type
|
||||
*/
|
||||
$("fieldset.workflow input[type='radio']").on('click',function(){
|
||||
operation = $(this).val();
|
||||
next = "approve/"+operation;
|
||||
$("#workflow_approve").attr("href",next);
|
||||
});
|
||||
try{
|
||||
$("div.inline-group table tbody tr.form-row").removeClass('has_original');
|
||||
$("div.inline-group table tbody tr.form-row td:first-child").find('p').hide();
|
||||
}catch(e){
|
||||
|
||||
}
|
||||
});
|
||||
})(django.jQuery);
|
||||
@@ -0,0 +1,86 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ready(fn) {
|
||||
if (document.readyState !== 'loading') {
|
||||
fn();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
var collapsed = document.body.classList.toggle('sidebar-collapsed');
|
||||
try {
|
||||
localStorage.setItem('erp-sidebar-collapsed', collapsed ? '1' : '0');
|
||||
} catch (e) {}
|
||||
updateCollapseLabel();
|
||||
}
|
||||
|
||||
function updateCollapseLabel() {
|
||||
var btn = document.getElementById('sidebarCollapseBtn');
|
||||
if (!btn) {
|
||||
return;
|
||||
}
|
||||
var collapsed = document.body.classList.contains('sidebar-collapsed');
|
||||
btn.innerHTML = collapsed
|
||||
? '<i>→</i> 展开菜单'
|
||||
: '<i>←</i> 收起菜单';
|
||||
}
|
||||
|
||||
function restoreSidebarState() {
|
||||
try {
|
||||
if (localStorage.getItem('erp-sidebar-collapsed') === '1') {
|
||||
document.body.classList.add('sidebar-collapsed');
|
||||
}
|
||||
} catch (e) {}
|
||||
updateCollapseLabel();
|
||||
}
|
||||
|
||||
function toggleUserMenu() {
|
||||
var menu = document.getElementById('userDropdown');
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
var open = menu.classList.toggle('open');
|
||||
if (open) {
|
||||
document.addEventListener('click', closeUserMenuOutside);
|
||||
} else {
|
||||
document.removeEventListener('click', closeUserMenuOutside);
|
||||
}
|
||||
}
|
||||
|
||||
function closeUserMenuOutside(e) {
|
||||
var menu = document.getElementById('userDropdown');
|
||||
var profile = document.getElementById('userProfileToggle');
|
||||
if (!menu || !profile) {
|
||||
return;
|
||||
}
|
||||
if (!menu.contains(e.target) && !profile.contains(e.target)) {
|
||||
menu.classList.remove('open');
|
||||
document.removeEventListener('click', closeUserMenuOutside);
|
||||
}
|
||||
}
|
||||
|
||||
function bindClick(id, handler) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handler(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
restoreSidebarState();
|
||||
|
||||
bindClick('topbarMenuBtn', toggleSidebar);
|
||||
bindClick('sidebarCollapseBtn', toggleSidebar);
|
||||
bindClick('topbarNotifyBtn', function () {
|
||||
window.location.href = '/admin/workflow/todolist/';
|
||||
});
|
||||
bindClick('userProfileToggle', toggleUserMenu);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Created by Administrator on 15-5-23.
|
||||
*/
|
||||
(function($) {
|
||||
$(document).ready(function() {
|
||||
/**
|
||||
set operation type
|
||||
*/
|
||||
$("#woextravalue_set-group select").attr('readonly','true')
|
||||
});
|
||||
})(django.jQuery);
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
default_app_config = 'syscfg.apps.SysConfig'
|
||||
@@ -0,0 +1,53 @@
|
||||
# coding=utf-8
|
||||
from django.contrib import admin
|
||||
from django.forms import ModelForm,DateField
|
||||
from syscfg.models import *
|
||||
from common import generic
|
||||
|
||||
|
||||
class SiteForm(ModelForm):
|
||||
"""
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SiteAdmin(admin.ModelAdmin):
|
||||
list_per_page = 10
|
||||
list_display = ['name', 'begin', 'end']
|
||||
fields = (('begin', 'end'), 'name', 'description', 'user')
|
||||
filter_horizontal = ['user']
|
||||
form = SiteForm
|
||||
|
||||
|
||||
class ModuleAdmin(generic.BOAdmin):
|
||||
CODE_NUMBER_WIDTH = 3
|
||||
CODE_PREFIX = 'U'
|
||||
list_display = ['code','name','parent','status']
|
||||
ordering = ['weight']
|
||||
raw_id_fields = ['parent']
|
||||
|
||||
|
||||
class MenuAdmin(generic.BOAdmin):
|
||||
CODE_NUMBER_WIDTH = 3
|
||||
CODE_PREFIX = 'M'
|
||||
|
||||
list_display = ['code','name','module','status']
|
||||
list_filter = ['module']
|
||||
ordering = ['weight']
|
||||
raw_id_fields = ['module']
|
||||
|
||||
|
||||
class RoleAdmin(generic.BOAdmin):
|
||||
CODE_NUMBER_WIDTH = 3
|
||||
CODE_PREFIX = 'R'
|
||||
list_display = ['code','name','status']
|
||||
filter_horizontal = ['users','menus']
|
||||
|
||||
|
||||
admin.site.register(Site, SiteAdmin)
|
||||
admin.site.register(Module,ModuleAdmin)
|
||||
admin.site.register(Menu,MenuAdmin)
|
||||
admin.site.register(Role,RoleAdmin)
|
||||
@@ -0,0 +1,9 @@
|
||||
__author__ = 'zhugl'
|
||||
# created at 15-4-22
|
||||
from django.apps.config import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class SysConfig(AppConfig):
|
||||
name = 'syscfg'
|
||||
verbose_name = _('SysConfig')
|
||||
@@ -0,0 +1,79 @@
|
||||
# coding=utf-8
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common import const
|
||||
from common import generic
|
||||
|
||||
|
||||
class Site(models.Model):
|
||||
"""
|
||||
站点,一个站点下可有多个公司,处于同一个站点下的用户逻辑上位于同一个组织
|
||||
"""
|
||||
index_weight = 1
|
||||
begin = models.DateField(_('begin date'), blank=True,null=True)
|
||||
end = models.DateField(_('end date'), blank=True,null=True)
|
||||
name = models.CharField(_('site name'), max_length=const.DB_CHAR_NAME_40)
|
||||
description = models.TextField(_('site description'),blank=True,null=True)
|
||||
user = models.ManyToManyField(User,verbose_name=_('administrator'))
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s'%self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Site')
|
||||
verbose_name_plural = _('Site')
|
||||
|
||||
|
||||
class Module(generic.BO):
|
||||
"""
|
||||
模块管理
|
||||
"""
|
||||
index_weight = 2
|
||||
code = models.CharField(_("module code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||
name = models.CharField(_("module name"),max_length=const.DB_CHAR_NAME_40)
|
||||
url = models.URLField(_("module url"),blank=True,null=True,max_length=const.DB_CHAR_NAME_80)
|
||||
weight = models.IntegerField(_("weight"),blank=True,null=True,default=99)
|
||||
icon = models.CharField(_("style class"),blank=True,null=True,max_length=const.DB_CHAR_NAME_40)
|
||||
parent = models.ForeignKey('self',blank=True,null=True,verbose_name=_("parent"))
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("module")
|
||||
verbose_name_plural = _("module")
|
||||
|
||||
|
||||
class Menu(generic.BO):
|
||||
"""
|
||||
菜单管理
|
||||
"""
|
||||
index_weight = 3
|
||||
module = models.ForeignKey(Module,verbose_name=_("module"))
|
||||
code = models.CharField(_("menu code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||
name = models.CharField(_("menu name"),max_length=const.DB_CHAR_NAME_40)
|
||||
url = models.URLField(_("menu url"),blank=True,null=True,max_length=const.DB_CHAR_NAME_80)
|
||||
weight = models.IntegerField(_("weight"),blank=True,null=True,default=99)
|
||||
icon = models.CharField(_("style class"),blank=True,null=True,max_length=const.DB_CHAR_NAME_40)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("menu")
|
||||
verbose_name_plural = _("menu")
|
||||
|
||||
|
||||
class Role(generic.BO):
|
||||
"""
|
||||
角色管理,分配用户所拥有的菜单
|
||||
"""
|
||||
index_weight = 4
|
||||
code = models.CharField(_("role code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||
name = models.CharField(_("role name"),max_length=const.DB_CHAR_NAME_40)
|
||||
description = models.CharField(_("description"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||
status = models.BooleanField(_("in use"),default=True)
|
||||
parent = models.ForeignKey('self',blank=True,null=True,verbose_name=_("parent"))
|
||||
users = models.ManyToManyField(User,verbose_name=_("role users"),blank=True)
|
||||
menus = models.ManyToManyField(Menu,verbose_name=_("role menus"),blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("role")
|
||||
verbose_name_plural = _("role")
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||