|  | 
from odoo import fields, models, api
from odoo.exceptions import UserError
from odoo.tools import float_compare
# 订单确认状态可选值
SELL_ORDER_STATES = [
    ('draft', '草稿'),
    ('done', '已确认'),
    ('cancel', '已作废')]
class SellAdjust(models.Model):
    _name = "sell.adjust"
    _inherit = ['mail.thread']
    _description = "销售变更单"
    _order = 'date desc, id desc'
    name = fields.Char('单据编号', copy=False,
                       help='变更单编号,保存时可自动生成')
    order_id = fields.Many2one('sell.order', '原始单据',
                               copy=False, ondelete='restrict',
                               help='要调整的原始销售订单,只能调整已确认且没有全部出库的销售订单')
    date = fields.Date('单据日期',
                       default=lambda self: fields.Date.context_today(self),
                       index=True, copy=False,
                       help='变更单创建日期,默认是当前日期')
    change_type = fields.Selection([('price', '价格'), ('qty', '数量')], '调整类型', default='qty')
    line_ids = fields.One2many('sell.adjust.line', 'order_id', '变更单行',
                               copy=True,
                               help='变更单明细行,不允许为空')
    approve_uid = fields.Many2one('res.users', '确认人',
                                  copy=False, ondelete='restrict',
                                  help='确认变更单的人')
    state = fields.Selection(SELL_ORDER_STATES, '确认状态',
                             index=True, copy=False,
                             default='draft',
                             tracking=True,
                             help='变更单确认状态')
    note = fields.Text('备注',
                       help='单据备注')
    user_id = fields.Many2one(
        'res.users',
        '经办人',
        ondelete='restrict',
        default=lambda self: self.env.user,
        help='单据经办人',
    )
    company_id = fields.Many2one(
        'res.company',
        string='公司',
        change_default=True,
        default=lambda self: self.env.company)
    @api.onchange('order_id')
    def onchange_sell_order(self):
        if not self.order_id:
            return {}
        self.line_ids = False
        change_lines = []
        for line in self.order_id.line_ids:
            vals = {
                'quantity': 0,
                'goods_id': line.goods_id.id,
                'using_attribute': line.using_attribute,
                'attribute_id': line.attribute_id.id,
                'uom_id': line.uom_id.id,
                'price': line.price,
                'price_taxed': line.price_taxed,
                'discount_rate': line.discount_rate,
                'discount_amount': line.discount_amount,
                'amount': line.amount,
                'tax_rate': line.tax_rate,
                'note': line.note,
                'company_id': line.company_id.id,
            }
            change_lines.append((0, 0, vals))
        self.line_ids = change_lines
    def check_order(self):
        self.ensure_one()
        if self.state == 'done':
            raise UserError('请不要重复确认!')
        if not self.line_ids:
            raise UserError('请输入商品明细行!')
        for line in self.line_ids:
            # 调价格不能有数量变更
            if self.change_type == 'price' and line.quantity:
                raise UserError('调整价格时不能调整数量!')
            # 调数量不能有价格变更
            if self.change_type == 'qty':
                origin_line = self.env['sell.order.line'].search(
                    [('goods_id', '=', line.goods_id.id),
                     ('attribute_id', '=', line.attribute_id.id),
                     ('order_id', '=', self.order_id.id)])
                if len(origin_line) > 1:
                    raise UserError('要调整的商品 %s 在原始单据中不唯一' % line.goods_id.name)
                if round(origin_line.price - line.price, 6):
                    raise UserError('调整数量时不能调整单价!')
    def sell_adjust_done(self):
        #二种调整不能同时进行,人算不过来
        self.check_order()
        if self.change_type == 'qty':
            self.change_qty()
        if self.change_type == 'price':
            self.change_price()
        self.state = 'done'
        self.approve_uid = self._uid
    def change_price(self):
        '''
            确认销售数量单价变更单:
            当结算单有付款或发票号,报错。
            其他都可以改。
        '''
        for line in self.line_ids:
            origin_line = self.env['sell.order.line'].search(
                [('goods_id', '=', line.goods_id.id),
                 ('attribute_id', '=', line.attribute_id.id),
                 ('order_id', '=', self.order_id.id)])
            if not origin_line:
                raise UserError('调整价格时不能新增行!')
            #变更销售单
            origin_line_amount = round(line.price * origin_line.quantity,2)
            origin_line_subtotal = round(line.price_taxed * origin_line.quantity,2)
            note = origin_line.note or ''
            note += '变更单:%s单价%s变更%s。\n' % (self.name, origin_line.price, line.price)
            origin_line.write({
                'price': line.price,
                'price_taxed': line.price_taxed,
                'amount': origin_line_amount,
                'tax_amount': origin_line_subtotal - origin_line_amount,
                'subtotal': origin_line_subtotal,
                'note': note,
            })
            #变更送货单
            move_line = self.env['wh.move.line'].search(
                [('sell_line_id', '=', origin_line.id)])
            move_line_amount = round(line.price * move_line.goods_qty, 2)
            move_line_subtotal = round(line.price_taxed * move_line.goods_qty, 2)
            move_line.write({
                'price': line.price,
                'price_taxed': line.price_taxed,
                'amount': move_line_amount,
                'tax_amount': move_line_subtotal - move_line_amount,
                'subtotal': move_line_subtotal,
                'note': note,
            })
            # 删除结算单
            invoice_id = self.env['money.invoice'].search([('move_id', '=', move_line.move_id.id)])
            if invoice_id and (invoice_id.reconciled or invoice_id.bill_number):
                raise UserError('已付款或有发票号码的订单不能变更价格!')
            if invoice_id:
                invoice_id.unlink()
        #如果送货单无对账单,重新生成
        delivery_ids = self.env['sell.delivery'].search(
            [('order_id', '=', self.order_id.id),
             ('state', '=', 'done')])
        for delivery_id in delivery_ids:
            if not delivery_id.invoice_id:
                delivery_id._delivery_make_invoice()
    def change_qty(self):
        '''确认销售数量变更单:
        当调整后数量 < 原单据中已出库数量,则报错;
        当调整后数量 > 原单据中已出库数量,则更新原单据及发货单分单的数量;
        当调整后数量 = 原单据中已出库数量,则更新原单据数量,删除发货单分单;
        当新增商品时,则更新原单据及发货单分单明细行。
        '''
        delivery = self.env['sell.delivery'].search(
            [('order_id', '=', self.order_id.id),
             ('state', '=', 'draft')])
        if not delivery:
            raise UserError('销售发货单已全部出库,不能调整')
        for line in self.line_ids:
            # 检查属性是否填充,防止无权限人员不填就可以保存
            if line.using_attribute and not line.attribute_id:
                raise UserError('请输入商品:%s 的属性' % line.goods_id.name)
            origin_line = self.env['sell.order.line'].search(
                [('goods_id', '=', line.goods_id.id),
                 ('attribute_id', '=', line.attribute_id.id),
                 ('order_id', '=', self.order_id.id)])
            if len(origin_line) > 1:
                raise UserError('要调整的商品 %s 在原始单据中不唯一' % line.goods_id.name)
            if origin_line:
                origin_line.quantity += line.quantity  # 调整后数量
                new_note = '变更单:%s %s。\n' % (self.name, line.note)
                origin_line.note = (origin_line.note and
                                    origin_line.note + new_note or new_note)
                if origin_line.quantity < origin_line.quantity_out:
                    raise UserError(' %s 调整后数量不能小于原订单已出库数量' %
                                    line.goods_id.name)
                elif origin_line.quantity > origin_line.quantity_out:
                    # 查找出原销售订单产生的草稿状态的发货单明细行,并更新它
                    move_line = self.env['wh.move.line'].search(
                        [('sell_line_id', '=', origin_line.id),
                         ('state', '=', 'draft')])
                    if move_line:
                        move_line.goods_qty += line.quantity
                    else:
                        raise UserError('商品 %s 已全部入库,建议新建采购订单' %
                                        line.goods_id.name)
                # 调整后数量与已出库数量相等时,删除产生的发货单分单
                else:
                    # 先删除对应的发货单行
                    move_line = self.env['wh.move.line'].search(
                        [('sell_line_id', '=', origin_line.id), ('state', '=',
                                                                 'draft')])
                    if move_line:
                        move_line.unlink()
                    # 如果发货单明细没有了,则删除发货单
                    if len(delivery.sell_move_id.line_out_ids) == 0:
                        delivery.unlink()
            else:
                vals = {
                    'order_id': self.order_id.id,
                    'goods_id': line.goods_id.id,
                    'attribute_id': line.attribute_id.id,
                    'quantity': line.quantity,
                    'uom_id': line.uom_id.id,
                    'price_taxed': line.price_taxed,
                    'discount_rate': line.discount_rate,
                    'discount_amount': line.discount_amount,
                    'tax_rate': line.tax_rate,
                    'note': line.note or '',
                }
                new_line = self.env['sell.order.line'].create(vals)
                delivery_line = []
                if line.goods_id.force_batch_one:
                    i = 0
                    while i < line.quantity:
                        i += 1
                        delivery_line.append(
                            self.order_id.get_delivery_line(new_line, single=True))
                else:
                    delivery_line.append(
                        self.order_id.get_delivery_line(new_line, single=False))
                delivery.write(
                    {'line_out_ids': [(0, 0, li) for li in delivery_line]})
class SellAdjustLine(models.Model):
    _name = 'sell.adjust.line'
    _description = '销售变更单明细'
    @api.depends('goods_id')
    def _compute_using_attribute(self):
        '''返回订单行中商品是否使用属性'''
        for l in self:
            l.using_attribute = l.goods_id.attribute_ids and True or False
    @api.depends('quantity', 'price_taxed', 'discount_amount', 'tax_rate')
    def _compute_all_amount(selfs):
        '''当订单行的数量、单价、折扣额、税率改变时,改变采购金额、税额、价税合计'''
        for self in selfs:
            self.subtotal = self.price_taxed * self.quantity - self.discount_amount  # 价税合计
            self.tax_amount = self.subtotal / \
                (100 + self.tax_rate) * self.tax_rate  # 税额
            self.amount = self.subtotal - self.tax_amount  # 金额
    @api.onchange('price', 'tax_rate')
    def onchange_price(self):
        '''当订单行的不含税单价改变时,改变含税单价'''
        price = self.price_taxed / (1 + self.tax_rate * 0.01)  # 不含税单价
        decimal = self.env.ref('core.decimal_price')
        if float_compare(price, self.price, precision_digits=decimal.digits) != 0:
            self.price_taxed = self.price * (1 + self.tax_rate * 0.01)
    order_id = fields.Many2one('sell.adjust', '订单编号', index=True,
                               required=True, ondelete='cascade',
                               help='关联的变更单编号')
    goods_id = fields.Many2one('goods', '商品', ondelete='restrict',
                               help='商品')
    using_attribute = fields.Boolean('使用属性', compute=_compute_using_attribute,
                                     help='商品是否使用属性')
    attribute_id = fields.Many2one('attribute', '属性',
                                   ondelete='restrict',
                                   domain="[('goods_id', '=', goods_id)]",
                                   help='商品的属性,当商品有属性时,该字段必输')
    uom_id = fields.Many2one('uom', '单位', ondelete='restrict',
                             help='商品计量单位')
    quantity = fields.Float('调整数量',
                            default=1,
                            required=True,
                            digits='Quantity',
                            help='相对于原单据对应明细行的调整数量,可正可负')
    price = fields.Float('销售单价',
                         store=True,
                         digits='Price',
                         help='不含税单价,由含税单价计算得出')
    price_taxed = fields.Float('含税单价',
                               digits='Price',
                               help='含税单价,取自商品零售价')
    discount_rate = fields.Float('折扣率%',
                                 help='折扣率')
    discount_amount = fields.Float('折扣额',
                                   digits='Amount',
                                   help='输入折扣率后自动计算得出,也可手动输入折扣额')
    amount = fields.Float('金额',
                          compute=_compute_all_amount,
                          store=True,
                          digits='Amount',
                          help='金额  = 价税合计  - 税额')
    tax_rate = fields.Float('税率(%)', default=lambda self: self.env.user.company_id.import_tax_rate,
                            help='默认值取公司销项税率')
    tax_amount = fields.Float('税额',
                              compute=_compute_all_amount,
                              store=True,
                              digits='Amount',
                              help='由税率计算得出')
    subtotal = fields.Float('价税合计',
                            compute=_compute_all_amount,
                            store=True,
                            digits='Amount',
                            help='含税单价 乘以 数量')
    note = fields.Char('备注',
                       help='本行备注')
    company_id = fields.Many2one(
        'res.company',
        string='公司',
        change_default=True,
        default=lambda self: self.env.company)
    @api.onchange('goods_id')
    def onchange_goods_id(self):
        '''当订单行的商品变化时,带出商品上的单位、默认仓库、价格'''
        if self.goods_id:
            self.uom_id = self.goods_id.uom_id
            # 修正 单价 及含税单价问题
            self.price_taxed = self.goods_id.price * (1 + self.goods_id.tax_rate * 0.01) if self.goods_id.tax_rate else self.goods_id.price
            self.tax_rate = self.goods_id.get_tax_rate(self.goods_id, self.order_id.order_id.partner_id, 'sell')
    @api.onchange('quantity', 'price_taxed', 'discount_rate')
    def onchange_discount_rate(self):
        '''当数量、含税单价或优惠率发生变化时,优惠金额发生变化'''
        self.price = self.price_taxed / (1 + self.tax_rate * 0.01)
        self.discount_amount = (self.quantity * self.price *
                                self.discount_rate * 0.01)
    @api.constrains('tax_rate')
    def _check_(self):
        for record in self:
            if record.tax_rate > 100:
                raise UserError('税率不能输入超过100的数')
            if record.tax_rate < 0:
                raise UserError('税率不能输入负数')
            
    
 |