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.

251 line
11KB

  1. # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  3. from odoo import models, fields, api
  4. from odoo.exceptions import UserError
  5. from odoo.tools import float_is_zero
  6. class MonthProductCost(models.Model):
  7. _name = 'month.product.cost'
  8. _description = '每月发出成本'
  9. period_id = fields.Many2one('finance.period', string='会计期间')
  10. goods_id = fields.Many2one('goods', string="商品")
  11. period_begin_qty = fields.Float(
  12. string='期初数量', digits='Quantity')
  13. period_begin_cost = fields.Float(
  14. string='期初成本', digits='Amount',)
  15. current_period_out_qty = fields.Float(
  16. string='本期出库量', digits='Quantity')
  17. current_period_out_cost = fields.Float(
  18. string='本期出库成本', digits='Amount',)
  19. current_period_in_qty = fields.Float(
  20. string='本期入库量', digits='Quantity')
  21. current_period_in_cost = fields.Float(
  22. string='本期入库成本', digits='Amount',)
  23. current_period_remaining_qty = fields.Float(
  24. string='本期剩余数量', digits='Quantity')
  25. current_period_remaining_cost = fields.Float(
  26. string='剩余数量成本', digits='Amount',)
  27. company_id = fields.Many2one(
  28. 'res.company',
  29. string='公司',
  30. change_default=True,
  31. default=lambda self: self.env.company)
  32. def get_stock_qty(self, period_id):
  33. date_range = self.env['finance.period'].get_period_month_date_range(
  34. period_id)
  35. # 每个产品最多取两行,一个本月出库,一个本月入库
  36. # 如果一个产品在本月没有出入库记录,则不生成发出成本记录
  37. self.env.cr.execute('''
  38. SELECT line.goods_id as goods_id,
  39. line.type as type,
  40. sum(line.goods_qty) as qty,
  41. sum(line.cost) as cost
  42. FROM wh_move_line line
  43. LEFT JOIN warehouse wh_dest ON line.warehouse_dest_id = wh_dest.id
  44. LEFT JOIN warehouse wh ON line.warehouse_id = wh.id
  45. WHERE line.state = 'done'
  46. AND line.date >= '%s'
  47. AND line.date <= '%s'
  48. AND ((wh_dest.type='stock'AND wh.type!='stock') OR
  49. (wh_dest.type!='stock' AND wh.type='stock'))
  50. GROUP BY line.goods_id,line.type
  51. ''' % (date_range[0], date_range[1]))
  52. return self.env.cr.dictfetchall()
  53. def get_goods_last_period_remaining_qty(self, period_id, goods_id):
  54. """
  55. :param period_id: 传入当前所需的期间,根据这个期间找到对应的上一个期间
  56. :param goods_id: 出入 goods 精确找到 上一期间 对应的 month.product.cost 记录
  57. :return: 让上一期间的 剩余数量,和剩余数量成本 以字典 形式返回
  58. """
  59. last_period_remaining_qty = 0
  60. last_period_remaining_cost = 0
  61. # 查找 离输入期间最近的 对应产品的发出成本行
  62. last_month_product_cost_row = self.search(
  63. [('period_id.name', '<', period_id.name),
  64. ('goods_id', '=', goods_id)],
  65. limit=1,
  66. order='id desc')
  67. if last_month_product_cost_row:
  68. last_period_remaining_qty = \
  69. last_month_product_cost_row.current_period_remaining_qty
  70. last_period_remaining_cost = \
  71. last_month_product_cost_row.current_period_remaining_cost
  72. return {
  73. 'last_period_remaining_qty': last_period_remaining_qty,
  74. 'last_period_remaining_cost': last_period_remaining_cost
  75. }
  76. def fill_in_out(self, dcit_goods):
  77. """
  78. 填充产品的本月出库入库数量和成本
  79. 这里填充的是实际出库成本,后面会调整成按 公司成本核算方式 计算的成本
  80. """
  81. if dcit_goods.get('type') == 'in':
  82. res = {'current_period_in_qty': dcit_goods.get('qty', 0),
  83. 'current_period_in_cost': dcit_goods.get('cost', 0)}
  84. else:
  85. res = {'current_period_out_qty': dcit_goods.get('qty'),
  86. 'current_period_out_cost': dcit_goods.get('cost', 0)}
  87. return res
  88. def month_remaining_qty_cost(self, goods_qty_cost):
  89. """
  90. 算出 本月的剩余的数量和成本
  91. """
  92. sum_goods_qty = goods_qty_cost.get('period_begin_qty', 0) - \
  93. goods_qty_cost.get('current_period_out_qty', 0) + \
  94. goods_qty_cost.get('current_period_in_qty', 0)
  95. sum_goods_cost = goods_qty_cost.get('period_begin_cost', 0) - \
  96. goods_qty_cost.get('current_period_out_cost', 0) + \
  97. goods_qty_cost.get('current_period_in_cost', 0)
  98. return {'current_period_remaining_qty': sum_goods_qty,
  99. 'current_period_remaining_cost': sum_goods_cost
  100. }
  101. def _get_cost_method(self, goods_id):
  102. '''
  103. 批次管理的产品使用个别计价
  104. 先取产品的计价方式,再取公司上的计价方式
  105. '''
  106. goods = self.env['goods'].browse(goods_id)
  107. if goods.using_batch:
  108. return 'fifo'
  109. if goods.cost_method:
  110. return goods.cost_method
  111. else:
  112. return self.env.user.company_id.cost_method
  113. def compute_balance_price(self, data_dict):
  114. """
  115. 可以用其他算法计算发出成本
  116. """
  117. cost_method = self._get_cost_method(data_dict.get("goods_id"))
  118. if cost_method == 'average':
  119. # 本月该商品的结存单价 = (上月该商品的成本余额 + 本月入库成本 )/ (上月数量余额 + 本月入库数量)
  120. # 则本月发出成本 = 结存单价 * 发出数量
  121. balance_price = (
  122. data_dict.get("period_begin_cost", 0)
  123. + data_dict.get("current_period_in_cost", 0)) / \
  124. ((
  125. data_dict.get("period_begin_qty", 0)
  126. + data_dict.get("current_period_in_qty", 0)) or 1)
  127. month_cost = balance_price * \
  128. data_dict.get("current_period_out_qty", 0)
  129. if cost_method == 'fifo':
  130. # 实际成本
  131. month_cost = data_dict.get("current_period_out_cost", 0)
  132. if cost_method == 'std':
  133. # 定额成本
  134. goods = self.env['goods'].browse(data_dict.get("goods_id"))
  135. month_cost = goods.price * \
  136. data_dict.get("current_period_out_qty", 0)
  137. return round(month_cost, 2)
  138. def create_month_product_cost_voucher(
  139. self, period_id, date,
  140. month_product_cost_dict):
  141. """
  142. 月底成本结转生成的凭证,算出借贷方金额后,
  143. 借贷方金额全部减去本期间库存商品科目(所有商品类别涉及的科目)贷方金额合计
  144. :param period_id:
  145. :param date:
  146. :param month_product_cost_dict:
  147. :return:
  148. """
  149. voucher_line_data_list = []
  150. account_row = self.env.user.company_id.cogs_account
  151. all_balance_price = 0
  152. for create_vals in list(month_product_cost_dict.values()):
  153. goods_row = self.env['goods'].browse(create_vals.get('goods_id'))
  154. current_period_out_cost = self.compute_balance_price(
  155. create_vals) # 当期加权平均成本
  156. # 发出时已结转的实际成本
  157. real_out_cost = create_vals.get('current_period_out_cost', 0)
  158. diff_cost = current_period_out_cost - real_out_cost # 两者之差
  159. if not float_is_zero(diff_cost, 2): # 贷方
  160. voucher_line_data = {
  161. 'name': '发出成本', 'credit': diff_cost,
  162. 'account_id': goods_row.category_id.account_id.id,
  163. 'goods_id': create_vals.get('goods_id'),
  164. 'goods_qty': create_vals.get('current_period_out_qty')}
  165. voucher_line_data_list.append([0, 0, voucher_line_data.copy()])
  166. all_balance_price += diff_cost
  167. # 创建 发出成本
  168. create_vals.update({
  169. 'current_period_out_cost': current_period_out_cost,
  170. 'current_period_remaining_cost':
  171. create_vals.get('period_begin_cost', 0) +
  172. create_vals.get('current_period_in_cost', 0) -
  173. current_period_out_cost
  174. })
  175. self.create(create_vals)
  176. if round(all_balance_price, 2) != 0: # 借方
  177. voucher_line_data_list.append(
  178. [0, 0, {
  179. 'name': '发出成本',
  180. 'account_id': account_row.id,
  181. 'debit': all_balance_price}])
  182. if voucher_line_data_list:
  183. voucher_id = self.env['voucher'].create({
  184. 'date': date, 'period_id': period_id.id,
  185. 'line_ids': voucher_line_data_list,
  186. 'is_checkout': True})
  187. voucher_id.voucher_done()
  188. def data_structure(self, list_dict_data, period_id):
  189. """
  190. 把 list_dict_data 按产品合并成 month_product_cost_dict,并填充期初、期末
  191. """
  192. month_product_cost_dict = {}
  193. for dict_goods in list_dict_data:
  194. period_begin_qty_cost = self.get_goods_last_period_remaining_qty(
  195. period_id, dict_goods.get('goods_id'))
  196. vals = {}
  197. if dict_goods.get('goods_id') not in month_product_cost_dict:
  198. vals = {'goods_id': dict_goods.get('goods_id'),
  199. 'period_id': period_id.id,
  200. 'period_begin_qty': period_begin_qty_cost.get(
  201. 'last_period_remaining_qty', 0),
  202. 'period_begin_cost': period_begin_qty_cost.get(
  203. 'last_period_remaining_cost', 0)}
  204. vals.update(self.fill_in_out(dict_goods))
  205. month_product_cost_dict.update(
  206. {dict_goods.get('goods_id'): vals.copy()})
  207. month_product_cost_dict.get(dict_goods.get('goods_id')).update(
  208. self.month_remaining_qty_cost(
  209. month_product_cost_dict.get(
  210. dict_goods.get('goods_id'))))
  211. else:
  212. vals.update(self.fill_in_out(dict_goods))
  213. month_product_cost_dict.get(
  214. dict_goods.get('goods_id')).update(vals.copy())
  215. month_product_cost_dict.get(dict_goods.get('goods_id')).update(
  216. self.month_remaining_qty_cost(
  217. month_product_cost_dict.get(
  218. dict_goods.get('goods_id'))))
  219. return month_product_cost_dict
  220. def generate_issue_cost(self, period_id, date):
  221. """
  222. 生成成本的凭证
  223. :param period_id:
  224. :return:
  225. """
  226. list_dict_data = self.get_stock_qty(period_id)
  227. issue_cost_exists = self.search([('period_id', '=', period_id.id)])
  228. issue_cost_exists.unlink()
  229. self.create_month_product_cost_voucher(
  230. period_id, date, self.data_structure(list_dict_data, period_id))
上海开阖软件有限公司 沪ICP备12045867号-1