GoodERP
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

749 lines
35KB

  1. from odoo import fields, models, api, _
  2. import datetime
  3. from odoo.exceptions import UserError
  4. from odoo.tools import float_compare, float_is_zero
  5. ISODATEFORMAT = '%Y-%m-%d'
  6. class SellDelivery(models.Model):
  7. _name = 'sell.delivery'
  8. _inherits = {'wh.move': 'sell_move_id'}
  9. _inherit = ['mail.thread', 'barcodes.barcode_events_mixin', 'mail.activity.mixin']
  10. _description = '销售发货单'
  11. _order = 'date desc, id desc'
  12. @api.depends('delivery_ids')
  13. def _compute_delivery(self):
  14. for order in self:
  15. order.return_count = len([deli for deli in order.delivery_ids if deli.is_return])
  16. @api.depends('line_out_ids.subtotal', 'discount_amount', 'partner_cost',
  17. 'receipt', 'partner_id', 'line_in_ids.subtotal', 'currency_id')
  18. def _compute_all_amount(self):
  19. """当优惠金额改变时,改变成交金额"""
  20. for s in self:
  21. total = 0
  22. if s.line_out_ids:
  23. # 发货时优惠前总金
  24. total = sum(line.subtotal for line in s.line_out_ids)
  25. for line in s.line_out_ids:
  26. line.currency_id = s.currency_id
  27. elif s.line_in_ids:
  28. # 退货时优惠前总金额
  29. total = sum(line.subtotal for line in s.line_in_ids)
  30. for line in s.line_in_ids:
  31. line.currency_id = s.currency_id
  32. s.amount = total - s.discount_amount
  33. # 获取当前汇率,用来计算当前的用本位币表示的成交金额。
  34. s.currency_rate = s.env['res.currency'].get_rate_silent(s.date, s.currency_id.id) or 1
  35. s.standard_amount = s.amount * s.currency_rate
  36. @api.depends('is_return', 'invoice_id.reconciled', 'invoice_id.amount')
  37. def _get_sell_money_state(self):
  38. """返回收款状态"""
  39. for s in self:
  40. decimal_amount = self.env.ref('core.decimal_amount')
  41. if not s.is_return:
  42. if s.invoice_id.reconciled == 0:
  43. s.money_state = '未收款'
  44. elif float_compare(s.invoice_id.amount, s.invoice_id.reconciled,
  45. precision_digits=decimal_amount.digits) == 1:
  46. s.money_state = '部分收款'
  47. elif float_compare(s.invoice_id.reconciled, s.invoice_id.amount,
  48. precision_digits=decimal_amount.digits) == 0:
  49. s.money_state = '全部收款'
  50. # 返回退款状态
  51. if s.is_return:
  52. if s.invoice_id.reconciled == 0:
  53. s.return_state = '未退款'
  54. elif float_compare(abs(s.invoice_id.amount), abs(s.invoice_id.reconciled),
  55. precision_digits=decimal_amount.digits) == 1:
  56. s.return_state = '部分退款'
  57. elif float_compare(s.invoice_id.reconciled, s.invoice_id.amount,
  58. precision_digits=decimal_amount.digits) == 0:
  59. s.return_state = '全部退款'
  60. currency_id = fields.Many2one('res.currency', '外币币别', readonly=True,
  61. help='外币币别')
  62. currency_rate = fields.Float('汇率', digits='Price')
  63. is_multi_currency = fields.Boolean('多币种', related='company_id.is_multi_currency')
  64. sell_move_id = fields.Many2one('wh.move', '发货单', required=True,
  65. ondelete='cascade',
  66. help='发货单号')
  67. is_return = fields.Boolean('是否退货', default=lambda self:
  68. self.env.context.get('is_return'),
  69. help='是否为退货类型')
  70. order_id = fields.Many2one('sell.order', '订单号', copy=False,
  71. ondelete='cascade',
  72. help='产生发货单/退货单的销售订单')
  73. invoice_id = fields.Many2one('money.invoice', '发票号',
  74. copy=False, ondelete='set null',
  75. help='产生的发票号')
  76. date_due = fields.Date('到期日期', copy=False,
  77. default=lambda self: fields.Date.context_today(
  78. self),
  79. help='收款截止日期')
  80. discount_rate = fields.Float('优惠率(%)',
  81. help='整单优惠率')
  82. discount_amount = fields.Float('优惠金额',
  83. digits='Amount',
  84. help='整单优惠金额,可由优惠率自动计算得出,也可手动输入')
  85. amount = fields.Float('成交金额', compute=_compute_all_amount,
  86. store=True, readonly=True,
  87. digits='Amount',
  88. help='总金额减去优惠金额')
  89. standard_amount = fields.Float('本位币成交金额', compute=_compute_all_amount,
  90. store=True, readonly=True,
  91. digits='总数',
  92. help='本位币的成交金额')
  93. partner_cost = fields.Float('客户承担费用',
  94. digits='Amount',
  95. help='客户承担费用')
  96. receipt = fields.Float('本次收款',
  97. digits='Amount',
  98. help='本次收款金额')
  99. bank_account_id = fields.Many2one('bank.account',
  100. '结算账户', ondelete='restrict',
  101. help='用来核算和监督企业与其他单位或个人之间的债权债务的结算情况')
  102. cost_line_ids = fields.One2many('cost.line', 'sell_id', '销售费用',
  103. copy=False,
  104. help='销售费用明细行')
  105. money_state = fields.Char('收款状态', compute=_get_sell_money_state,
  106. store=True, default='未收款',
  107. help="销售发货单的收款状态", index=True, copy=False)
  108. return_state = fields.Char('退款状态', compute=_get_sell_money_state,
  109. store=True, default='未退款',
  110. help="销售退货单的退款状态", index=True, copy=False)
  111. contact = fields.Char('联系人',
  112. help='客户方的联系人')
  113. address_id = fields.Many2one('partner.address', '联系人地址',
  114. domain="[('partner_id','=',partner_id)]",
  115. help='联系地址')
  116. mobile = fields.Char('手机',
  117. help='联系手机')
  118. origin_id = fields.Many2one('sell.delivery', '来源单据', ondelete='restrict',)
  119. voucher_id = fields.Many2one('voucher', '出库凭证', readonly=True,
  120. help='发货时产生的出库凭证')
  121. money_order_id = fields.Many2one(
  122. 'money.order',
  123. '收款单',
  124. readonly=True,
  125. copy=False,
  126. help='输入本次收款确认时产生的收款单')
  127. project_id = fields.Many2one('project', string='项目')
  128. delivery_ids = fields.One2many(
  129. 'sell.delivery', 'origin_id', string='退货单', copy=False)
  130. return_count = fields.Integer(
  131. compute='_compute_delivery', string='退货单数量', default=0)
  132. def set_today(self):
  133. self.date = fields.Date.today()
  134. @api.onchange('address_id')
  135. def onchange_address_id(self):
  136. """ 选择地址填充 联系人、电话 """
  137. if self.address_id:
  138. self.contact = self.address_id.contact
  139. self.mobile = self.address_id.mobile
  140. @api.onchange('partner_id')
  141. def onchange_partner_id(self):
  142. """选择客户带出其默认地址信息"""
  143. if self.partner_id:
  144. self.contact = self.partner_id.contact
  145. self.mobile = self.partner_id.mobile
  146. self.currency_id = self.partner_id.c_category_id.account_id.currency_id or self.env.company.currency_id
  147. for child in self.partner_id.child_ids:
  148. if child.is_default_add:
  149. self.address_id = child.id
  150. if self.partner_id.child_ids and not any([child.is_default_add for child in self.partner_id.child_ids]):
  151. partners_add = self.env['partner.address'].search(
  152. [('partner_id', '=', self.partner_id.id)], order='id')
  153. self.address_id = partners_add[0].id
  154. for line in self.line_out_ids:
  155. line.tax_rate = line.goods_id.get_tax_rate(line.goods_id, self.partner_id, 'sell')
  156. address_list = [
  157. child_list.id for child_list in self.partner_id.child_ids]
  158. if address_list:
  159. return {'domain': {'address_id': [('id', 'in', address_list)]}}
  160. else:
  161. self.address_id = False
  162. @api.onchange('discount_rate', 'line_in_ids', 'line_out_ids')
  163. def onchange_discount_rate(self):
  164. """当优惠率或订单行发生变化时,单据优惠金额发生变化"""
  165. total = 0
  166. if self.line_out_ids:
  167. # 发货时优惠前总金额
  168. total = sum(line.subtotal for line in self.line_out_ids)
  169. elif self.line_in_ids:
  170. # 退货时优惠前总金额
  171. total = sum(line.subtotal for line in self.line_in_ids)
  172. if self.discount_rate:
  173. self.discount_amount = total * self.discount_rate * 0.01
  174. def get_move_origin(self, vals):
  175. return self._name + (self.env.context.get('is_return') and '.return'
  176. or '.sell')
  177. @api.model_create_multi
  178. def create(self, vals_list):
  179. """创建销售出货单时生成有序编号"""
  180. if not self.env.context.get('is_return'):
  181. name = self._name
  182. else:
  183. name = 'sell.return'
  184. for vals in vals_list:
  185. if vals.get('name', '/') == '/':
  186. vals['name'] = self.env['ir.sequence'].next_by_code(name) or '/'
  187. vals.update({
  188. 'origin': self.get_move_origin(vals),
  189. 'finance_category_id': self.env.ref('finance.categ_sell_goods').id,
  190. })
  191. return super(SellDelivery, self).create(vals_list)
  192. def unlink(self):
  193. return self.sell_move_id.unlink()
  194. def goods_inventory(self, vals):
  195. """
  196. 审核时若仓库中商品不足,则产生补货向导生成其他入库单并审核。
  197. :param vals: 创建其他入库单需要的字段及取值信息构成的字典
  198. :return:
  199. """
  200. auto_in = self.env['wh.in'].create(vals)
  201. line_ids = [line.id for line in auto_in.line_in_ids]
  202. self.with_context({'wh_in_line_ids': line_ids}).sell_delivery_done()
  203. return True
  204. def _wrong_delivery_done(self):
  205. self.ensure_one()
  206. """审核时不合法的给出报错"""
  207. if self.state == 'done':
  208. raise UserError('请不要重复发货')
  209. for line in self.line_in_ids:
  210. if line.goods_qty <= 0 or line.price_taxed < 0:
  211. raise UserError('商品 %s 的数量和商品含税单价不能小于0!' % line.goods_id.name)
  212. if not self.bank_account_id and self.receipt:
  213. raise UserError('收款额不为空时,请选择结算账户!')
  214. decimal_amount = self.env.ref('core.decimal_amount')
  215. if float_compare(self.receipt, self.amount + self.partner_cost, precision_digits=decimal_amount.digits) == 1:
  216. raise UserError('本次收款金额不能大于成交金额!\n本次收款金额:%s 成交金额:%s' %
  217. (self.receipt, self.amount + self.partner_cost))
  218. # TODO:客户有自己的发货时检查信用额度的逻辑,这里暂时去掉gooderp原生的检查
  219. return
  220. # 发库单/退货单 计算客户的 本次发货金额+客户应收余额 是否小于客户信用额度, 否则报错
  221. if not self.is_return:
  222. amount = self.amount + self.partner_cost
  223. if self.partner_id.credit_limit != 0:
  224. if float_compare(amount - self.receipt + self.partner_id.receivable, self.partner_id.credit_limit,
  225. precision_digits=decimal_amount.digits) == 1:
  226. raise UserError('本次发货金额 + 客户应收余额 - 本次收款金额 不能大于客户信用额度!\n\
  227. 本次发货金额:%s\n 客户应收余额:%s\n 本次收款金额:%s\n客户信用额度:%s' % (
  228. amount, self.partner_id.receivable, self.receipt, self.partner_id.credit_limit))
  229. def _line_qty_write(self):
  230. if self.order_id:
  231. for line in self.line_in_ids:
  232. if self.order_id.type == 'return':
  233. line.sell_line_id.quantity_out += line.goods_qty
  234. else:
  235. line.sell_line_id.quantity_out -= line.goods_qty
  236. for line in self.line_out_ids:
  237. decimal_quantity = self.env.ref('core.decimal_quantity')
  238. if float_compare(
  239. line.sell_line_id.quantity_out + line.goods_qty,
  240. line.sell_line_id.quantity,
  241. decimal_quantity.digits) == 1:
  242. if not line.goods_id.excess:
  243. raise UserError('%s发货数量大于订单数量' % line.goods_id.name)
  244. line.sell_line_id.write({'quantity_out':line.sell_line_id.quantity_out + line.goods_qty})
  245. return
  246. def _get_invoice_vals(self, partner_id, category_id, date, amount, tax_amount):
  247. """返回创建 money_invoice 时所需数据"""
  248. return {
  249. 'move_id': self.sell_move_id.id,
  250. 'name': self.name,
  251. 'partner_id': partner_id.id,
  252. 'pay_method': self.order_id.pay_method.id,
  253. 'category_id': category_id.id,
  254. 'date': date,
  255. 'amount': amount,
  256. 'reconciled': 0,
  257. 'to_reconcile': amount,
  258. 'tax_amount': tax_amount,
  259. 'date_due': self.date_due,
  260. 'state': 'draft',
  261. 'currency_id':
  262. self.currency_id.id if self.currency_id and self.currency_id != self.env.company.currency_id else False,
  263. }
  264. def _delivery_make_invoice(self):
  265. """发货单/退货单 生成结算单"""
  266. if not self.is_return:
  267. amount = self.amount + self.partner_cost
  268. tax_amount = sum(line.tax_amount for line in self.line_out_ids)
  269. else:
  270. amount = -(self.amount + self.partner_cost)
  271. tax_amount = - sum(line.tax_amount for line in self.line_in_ids)
  272. category = self.env.ref('money.core_category_sale')
  273. invoice_id = False
  274. if not float_is_zero(amount, 2):
  275. invoice_id = self.env['money.invoice'].create(
  276. self._get_invoice_vals(
  277. self.partner_id, category, self.date, amount, tax_amount)
  278. )
  279. return invoice_id
  280. def _sell_amount_to_invoice(self):
  281. """销售费用产生结算单"""
  282. invoice_id = False
  283. if sum(cost_line.amount for cost_line in self.cost_line_ids) > 0:
  284. for line in self.cost_line_ids:
  285. if not float_is_zero(line.amount, 2):
  286. invoice_id = self.env['money.invoice'].create(
  287. self._get_invoice_vals(
  288. line.partner_id, line.category_id, self.date, line.amount + line.tax, line.tax)
  289. )
  290. return invoice_id
  291. def _make_money_order(self, invoice_id, amount, this_reconcile):
  292. """生成收款单"""
  293. categ = self.env.ref('money.core_category_sale')
  294. money_lines = [{
  295. 'bank_id': self.bank_account_id.id,
  296. 'amount': this_reconcile,
  297. }]
  298. source_lines = [{
  299. 'name': invoice_id and invoice_id.id,
  300. 'category_id': categ.id,
  301. 'date': invoice_id and invoice_id.date,
  302. 'amount': amount,
  303. 'reconciled': 0.0,
  304. 'to_reconcile': amount,
  305. 'this_reconcile': this_reconcile,
  306. }]
  307. rec = self.with_context(type='get')
  308. money_order = rec.env['money.order'].create({
  309. 'partner_id': self.partner_id.id,
  310. 'date': self.date,
  311. 'line_ids': [(0, 0, line) for line in money_lines],
  312. 'source_ids': [(0, 0, line) for line in source_lines] if invoice_id.state == 'done' else False,
  313. 'amount': amount,
  314. 'reconciled': this_reconcile,
  315. 'to_reconcile': amount,
  316. 'state': 'draft',
  317. 'origin_name': self.name,
  318. 'sell_id': self.order_id.id,
  319. })
  320. return money_order
  321. def _create_voucher_line(self, account_id, voucher_id, goods_id,
  322. goods_qty, partner_id, currency_id, currency_amount):
  323. """
  324. 创建凭证明细行
  325. :param account_id: 科目
  326. :param currency_amount: 金额
  327. :param voucher_id: 凭证
  328. :param goods_id: 料品
  329. :param partner_id: 供应商
  330. :param currency_id: 币种
  331. :return:
  332. """
  333. if currency_id and currency_id != self.env.company.currency_id:
  334. voucher = self.env['voucher.line'].create({
  335. 'name': '%s %s' % (self.name, ''),
  336. 'account_id': account_id and account_id.id,
  337. 'partner_id': partner_id and partner_id.id,
  338. 'debit': 0 if currency_amount > 0 else -currency_amount * currency_id.rate,
  339. 'credit': currency_amount * currency_id.rate if currency_amount > 0 else 0,
  340. 'voucher_id': voucher_id and voucher_id.id,
  341. 'goods_id': goods_id and goods_id.id,
  342. 'goods_qty': goods_qty,
  343. 'currency_id': currency_id.id,
  344. 'currency_amount': currency_amount if currency_amount > 0 else -currency_amount,
  345. 'rate_silent': currency_id.rate,
  346. })
  347. else:
  348. voucher = self.env['voucher.line'].create({
  349. 'name': '%s %s' % (self.name, ''),
  350. 'account_id': account_id and account_id.id,
  351. 'partner_id': partner_id and partner_id.id,
  352. 'debit': 0 if currency_amount > 0 else -currency_amount,
  353. 'credit': currency_amount if currency_amount > 0 else 0,
  354. 'voucher_id': voucher_id and voucher_id.id,
  355. 'goods_id': goods_id and goods_id.id,
  356. 'goods_qty': goods_qty,
  357. })
  358. return voucher
  359. def create_voucher(self):
  360. """
  361. 销售出货单、退货单审核时生成会计凭证
  362. 借:主营业务成本(核算分类上会计科目)
  363. 贷:库存商品(商品分类上会计科目)
  364. 当一张发货单有多个商品的时候,按对应科目汇总生成多个贷方凭证行。
  365. 退货单生成的金额为负
  366. """
  367. self.ensure_one()
  368. voucher = self.env['voucher'].create({'date': self.date, 'ref': '%s,%s' % (self._name, self.id)})
  369. line_ids = self.is_return and self.line_in_ids or self.line_out_ids
  370. sum_amount = sum(line.cost for line in self.line_ids)
  371. if self.is_return:
  372. sum_amount = -sum_amount # 退货单生成的金额为负
  373. if sum_amount: # 借方明细
  374. # 凭证行的外币币别由会计科目的币别来确定
  375. self._create_voucher_line(self.sell_move_id.finance_category_id.account_id,
  376. voucher, False, 0, self.partner_id,
  377. self.sell_move_id.finance_category_id.account_id.currency_id, -sum_amount)
  378. for line in line_ids: # 发货单/退货单明细
  379. cost = self.is_return and -line.cost or line.cost
  380. if not cost:
  381. continue # 缺货审核发货单时不产生出库凭证
  382. else: # 贷方明细
  383. self._create_voucher_line(line.goods_id.category_id.account_id,
  384. voucher, line.goods_id, line.goods_qty, False,
  385. line.goods_id.category_id.account_id.currency_id, cost)
  386. if len(voucher.line_ids) > 0:
  387. voucher.voucher_done()
  388. return voucher
  389. else:
  390. voucher.unlink()
  391. def auto_reconcile_sell_order(self):
  392. """ 预收款与结算单自动核销 """
  393. self.ensure_one()
  394. all_delivery_amount = 0
  395. for delivery in self.order_id.delivery_ids:
  396. all_delivery_amount += delivery.amount
  397. if (self.order_id.received_amount and self.order_id.received_amount == all_delivery_amount and
  398. not self.env.user.company_id.draft_invoice): # 根据发票确认应收应付勾选时不自动核销
  399. adv_pay_result = []
  400. receive_source_result = []
  401. # 预收款
  402. adv_pay_orders = self.env['money.order'].search([('partner_id', '=', self.partner_id.id),
  403. ('type', '=', 'get'),
  404. ('state', '=', 'done'),
  405. ('to_reconcile',
  406. '!=', 0),
  407. ('sell_id', '=', self.order_id.id)])
  408. for order in adv_pay_orders:
  409. adv_pay_result.append((0, 0, {'name': order.id,
  410. 'amount': order.amount,
  411. 'date': order.date,
  412. 'reconciled': order.reconciled,
  413. 'to_reconcile': order.to_reconcile,
  414. 'this_reconcile': order.to_reconcile,
  415. }))
  416. # 结算单
  417. receive_source_name = [
  418. delivery.name for delivery in self.order_id.delivery_ids]
  419. receive_source_orders = self.env['money.invoice'].search([('category_id.type', '=', 'income'),
  420. ('partner_id', '=',
  421. self.partner_id.id),
  422. ('to_reconcile',
  423. '!=', 0),
  424. ('name', 'in', receive_source_name)])
  425. for invoice in receive_source_orders:
  426. receive_source_result.append((0, 0, {
  427. 'name': invoice.id,
  428. 'category_id': invoice.category_id.id,
  429. 'amount': invoice.amount,
  430. 'date': invoice.date,
  431. 'reconciled': invoice.reconciled,
  432. 'to_reconcile': invoice.to_reconcile,
  433. 'date_due': invoice.date_due,
  434. 'this_reconcile': invoice.to_reconcile,
  435. }))
  436. # 创建核销单
  437. reconcile_order = self.env['reconcile.order'].create({
  438. 'partner_id': self.partner_id.id,
  439. 'business_type': 'adv_pay_to_get',
  440. 'advance_payment_ids': adv_pay_result,
  441. 'receivable_source_ids': receive_source_result,
  442. 'note': '自动核销',
  443. })
  444. reconcile_order.reconcile_order_done() # 自动审核
  445. def sell_delivery_done(self):
  446. """审核销售出货单/退货单,更新本单的收款状态/退款状态,并生成结算单和收款单"""
  447. for record in self:
  448. record._wrong_delivery_done()
  449. # 库存不足 生成零的
  450. if self.env.user.company_id.is_enable_negative_stock:
  451. result_vals = self.env['wh.move'].create_zero_wh_in(
  452. record, record._name)
  453. if result_vals:
  454. return result_vals
  455. # 调用wh.move中审核方法,更新审核人和审核状态
  456. record.sell_move_id.approve_order()
  457. # 将发货/退货数量写入销售订单行
  458. if record.order_id:
  459. record._line_qty_write()
  460. voucher = False
  461. # 创建出库的会计凭证,生成盘盈的入库单的不产生出库凭证
  462. if not self.env.user.company_id.endmonth_generation_cost:
  463. voucher = record.create_voucher()
  464. record.voucher_id = voucher
  465. # 发货单/退货单 生成结算单
  466. invoice_id = record._delivery_make_invoice()
  467. # 销售费用产生结算单
  468. record._sell_amount_to_invoice()
  469. # 生成收款单,并审核
  470. money_order = False
  471. if record.receipt:
  472. flag = not record.is_return and 1 or -1
  473. amount = flag * (record.amount + record.partner_cost)
  474. this_reconcile = flag * record.receipt
  475. money_order = record._make_money_order(
  476. invoice_id, amount, this_reconcile)
  477. money_order.money_order_done()
  478. record.write({
  479. 'voucher_id': voucher and voucher.id,
  480. 'invoice_id': invoice_id and invoice_id.id,
  481. 'money_order_id': money_order and money_order.id,
  482. 'state': 'done', # 为保证审批流程顺畅,否则,未审批就可审核
  483. })
  484. # 先收款后发货订单自动核销
  485. self.auto_reconcile_sell_order()
  486. if record.order_id:
  487. # 如果已退货也已退款,不生成新的分单
  488. if record.is_return and record.receipt:
  489. return True
  490. # 产生新的销售发货单时,如果已经存在草稿的销售发货单时,先将已经存在的草稿发货单进行删除
  491. self.env['sell.delivery'].search(['&',('state', '=', 'draft'),'&',('order_id','=', record.order_id.id),('is_return', '=', False)]).unlink()
  492. return record.order_id.sell_generate_delivery()
  493. def sell_delivery_draft(self):
  494. """反审核销售出货单/退货单,提示本单的收款状态/退款状态,并删除生成的结算单及凭证"""
  495. self.ensure_one()
  496. if self.state == 'draft':
  497. raise UserError('请不要重复撤销 %s' % self._description)
  498. # 当此发货单有关联的退货单时,禁止撤销此发货单
  499. related_returns = self.env['sell.delivery'].search([('origin_id', '=', self.id)])
  500. if related_returns:
  501. raise UserError(
  502. _("此发货单关联退货单,不能撤销!\n关联的单据号:%s") % (', '.join(related_returns.mapped('name'))))
  503. # 查找产生的结算单(除了开给客户的发票还有可能是采购费用发票)
  504. invoice_ids = self.env['money.invoice'].search([('name', '=', self.name)])
  505. for invoice in invoice_ids:
  506. # 不能反审核已核销的发货单
  507. if invoice.reconciled != 0:
  508. raise UserError('发票已核销,不能撤销发货!')
  509. if invoice.state == 'done':
  510. if self.env.company.draft_invoice:
  511. raise UserError('发票已开不可撤销发货')
  512. invoice.money_invoice_draft()
  513. invoice.unlink()
  514. # 删除产生的出库凭证
  515. voucher = self.voucher_id
  516. if voucher and voucher.state == 'done':
  517. voucher.voucher_draft()
  518. voucher.unlink()
  519. self.write({
  520. 'state': 'draft',
  521. })
  522. # 将原始订单中已执行数量清零
  523. if self.order_id:
  524. for line in self.line_out_ids:
  525. line.sell_line_id.quantity_out -= line.goods_qty
  526. for line in self.line_in_ids:
  527. if self.order_id.type == 'return':
  528. line.sell_line_id.quantity_out -= line.goods_qty
  529. else:
  530. line.sell_line_id.quantity_out += line.goods_qty
  531. # 调用wh.move中反审核方法,更新审核人和审核状态
  532. self.sell_move_id.cancel_approved_order()
  533. return True
  534. def sell_to_return(self):
  535. """销售出货单转化为销售退货单"""
  536. return_goods = {}
  537. return_order_draft = self.search([
  538. ('is_return', '=', True),
  539. ('origin_id', '=', self.id),
  540. ('state', '=', 'draft')
  541. ])
  542. if return_order_draft:
  543. raise UserError('销售发货单存在草稿状态的退货单!')
  544. return_order = self.search([
  545. ('is_return', '=', True),
  546. ('origin_id', '=', self.id),
  547. ('state', '=', 'done')
  548. ])
  549. for order in return_order:
  550. for return_line in order.line_in_ids:
  551. # 用产品、属性、批次做key记录已退货数量
  552. t_key = (return_line.goods_id.id,
  553. return_line.attribute_id.id, return_line.lot)
  554. if return_goods.get(t_key):
  555. return_goods[t_key] += return_line.goods_qty
  556. else:
  557. return_goods[t_key] = return_line.goods_qty
  558. receipt_line = []
  559. for line in self.line_out_ids:
  560. qty = line.goods_qty
  561. l_key = (line.goods_id.id, line.attribute_id.id, line.lot_id.lot)
  562. if return_goods.get(l_key):
  563. qty = qty - return_goods[l_key]
  564. if qty > 0:
  565. dic = {
  566. 'goods_id': line.goods_id.id,
  567. 'attribute_id': line.attribute_id.id,
  568. 'uom_id': line.uom_id.id,
  569. 'warehouse_id': line.warehouse_dest_id.id,
  570. 'warehouse_dest_id': line.warehouse_id.id,
  571. 'goods_qty': qty,
  572. 'sell_line_id': line.sell_line_id.id,
  573. 'price_taxed': line.price_taxed,
  574. 'price': line.price,
  575. 'tax_rate':line.tax_rate,
  576. 'cost_unit': line.cost_unit,
  577. 'cost': line.cost,#退货取不到成本
  578. 'discount_rate': line.discount_rate,
  579. 'discount_amount': line.discount_amount,
  580. 'type': 'in',
  581. }
  582. if line.goods_id.using_batch:
  583. dic.update({'lot': line.lot_id.lot})
  584. receipt_line.append(dic)
  585. if len(receipt_line) == 0:
  586. raise UserError('该订单已全部退货!')
  587. vals = {'partner_id': self.partner_id.id,
  588. 'is_return': True,
  589. 'order_id': self.order_id.id,
  590. 'user_id': self.user_id.id,
  591. 'origin_id': self.id,
  592. 'origin': 'sell.delivery.return',
  593. 'warehouse_dest_id': self.warehouse_id.id,
  594. 'warehouse_id': self.warehouse_dest_id.id,
  595. 'bank_account_id': self.bank_account_id.id,
  596. 'date_due': (datetime.datetime.now()).strftime(ISODATEFORMAT),
  597. 'date': (datetime.datetime.now()).strftime(ISODATEFORMAT),
  598. 'line_in_ids': [(0, 0, line) for line in receipt_line],
  599. 'discount_amount': self.discount_amount,
  600. }
  601. delivery_return = self.with_context(is_return=True).create(vals)
  602. view_id = self.env.ref('sell.sell_return_form').id
  603. name = '销售退货单'
  604. return {
  605. 'name': name,
  606. 'view_mode': 'form',
  607. 'view_id': False,
  608. 'views': [(view_id, 'form')],
  609. 'res_model': 'sell.delivery',
  610. 'type': 'ir.actions.act_window',
  611. 'res_id': delivery_return.id,
  612. 'target': 'current'
  613. }
  614. def action_view_return(self):
  615. '''
  616. 该销售发货单对应的退货单
  617. '''
  618. self.ensure_one()
  619. action = {
  620. 'name': '销售退货单',
  621. 'type': 'ir.actions.act_window',
  622. 'view_type': 'form',
  623. 'view_mode': 'form',
  624. 'res_model': 'sell.delivery',
  625. 'view_id': False,
  626. 'target': 'current',
  627. }
  628. list_view_id = self.env.ref('sell.sell_return_list').id
  629. form_view_id = self.env.ref('sell.sell_return_form').id
  630. delivery_ids = [delivery.id for delivery in self.delivery_ids if delivery.is_return]
  631. if len(delivery_ids) > 1:
  632. action['domain'] = "[('id','in',[" + \
  633. ','.join(map(str, delivery_ids)) + "])]"
  634. action['view_mode'] = 'list,form'
  635. action['views'] = [(list_view_id, 'list'), (form_view_id, 'form')]
  636. elif len(delivery_ids) == 1:
  637. action['views'] = [(form_view_id, 'form')]
  638. action['res_id'] = delivery_ids and delivery_ids[0] or False
  639. return action
  640. class WhMoveLine(models.Model):
  641. _inherit = 'wh.move.line'
  642. sell_line_id = fields.Many2one('sell.order.line', '销售单行',
  643. ondelete='cascade',
  644. help='对应的销售订单行')
  645. @api.onchange('warehouse_id', 'goods_id')
  646. def onchange_warehouse_id(self):
  647. """当订单行的仓库变化时,带出定价策略中的折扣率"""
  648. if self.warehouse_id and self.goods_id:
  649. partner_id = self.env.context.get('default_partner')
  650. partner = self.env['partner'].browse(
  651. partner_id) or self.move_id.partner_id
  652. warehouse = self.warehouse_id
  653. goods = self.goods_id
  654. date = self.env.context.get('default_date') or self.move_id.date
  655. if self.env.context.get('warehouse_type') == 'customer' or \
  656. self.env.context.get('warehouse_dest_type') == 'customer':
  657. pricing = self.env['pricing'].get_pricing_id(
  658. partner, warehouse, goods, date)
  659. if pricing:
  660. self.discount_rate = pricing.discount_rate
  661. else:
  662. self.discount_rate = 0
  663. def _delivery_get_price_and_tax(self):
  664. self.tax_rate = self.env.user.company_id.output_tax_rate
  665. self.price_taxed = self.goods_id.price
  666. if self.env.context.get('order_id'):
  667. line = self.env['sell.order.line'].search([
  668. ('order_id', '=', self.env.context.get('order_id')),
  669. ('goods_id', '=', self.goods_id.id)
  670. ], limit=1)
  671. if line:
  672. self.sell_line_id = line.id
  673. self.uos_id = line.goods_id.uos_id.id
  674. self.uom_id = line.uom_id.id
  675. self.price = line.price
  676. self.price_taxed = line.price_taxed
  677. self.discount_rate = line.discount_rate
  678. self.tax_rate = line.tax_rate
  679. self.plan_date = line.order_id.delivery_date
  680. else:
  681. raise UserError('无此商品的订单行')
  682. @api.onchange('goods_id')
  683. def onchange_goods_id(self):
  684. """当订单行的料品变化时,带出料品上的零售价,以及公司的销项税"""
  685. self.ensure_one()
  686. is_return = self.env.context.get('default_is_return')
  687. if self.goods_id:
  688. # 如果是销售发货单行 或 销售退货单行
  689. if is_return is not None and \
  690. ((self.type == 'out' and not is_return) or (self.type == 'in' and is_return)):
  691. self._delivery_get_price_and_tax()
  692. return super(WhMoveLine, self).onchange_goods_id()
上海开阖软件有限公司 沪ICP备12045867号-1