stockor 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +220 -0
  5. data/Guardfile +13 -0
  6. data/README.md +7 -0
  7. data/Rakefile +7 -0
  8. data/client/skr/Extension.coffee +12 -0
  9. data/client/skr/components/.gitkeep +0 -0
  10. data/client/skr/components/address/Address.coffee +21 -0
  11. data/client/skr/components/address/address.html +20 -0
  12. data/client/skr/index.js +21 -0
  13. data/client/skr/models/Address.coffee +17 -0
  14. data/client/skr/models/Base.coffee +8 -0
  15. data/client/skr/models/Customer.coffee +26 -0
  16. data/client/skr/models/GlAccount.coffee +10 -0
  17. data/client/skr/models/GlManualEntry.coffee +11 -0
  18. data/client/skr/models/GlPeriod.coffee +10 -0
  19. data/client/skr/models/GlPosting.coffee +15 -0
  20. data/client/skr/models/GlTransaction.coffee +16 -0
  21. data/client/skr/models/IaLine.coffee +19 -0
  22. data/client/skr/models/IaReason.coffee +12 -0
  23. data/client/skr/models/InvLine.coffee +27 -0
  24. data/client/skr/models/InventoryAdjustment.coffee +17 -0
  25. data/client/skr/models/Invoice.coffee +31 -0
  26. data/client/skr/models/Location.coffee +15 -0
  27. data/client/skr/models/PaymentTerm.coffee +11 -0
  28. data/client/skr/models/PickTicket.coffee +19 -0
  29. data/client/skr/models/PoLine.coffee +27 -0
  30. data/client/skr/models/PoReceipt.coffee +20 -0
  31. data/client/skr/models/PorLine.coffee +26 -0
  32. data/client/skr/models/PtLine.coffee +27 -0
  33. data/client/skr/models/PurchaseOrder.coffee +23 -0
  34. data/client/skr/models/SalesOrder.coffee +32 -0
  35. data/client/skr/models/Sku.coffee +21 -0
  36. data/client/skr/models/SkuLoc.coffee +21 -0
  37. data/client/skr/models/SkuTran.coffee +23 -0
  38. data/client/skr/models/SkuVendor.coffee +19 -0
  39. data/client/skr/models/SoLine.coffee +27 -0
  40. data/client/skr/models/Uom.coffee +17 -0
  41. data/client/skr/models/Vendor.coffee +28 -0
  42. data/client/skr/models/VoLine.coffee +23 -0
  43. data/client/skr/models/Voucher.coffee +22 -0
  44. data/client/skr/models/mixins/CodeField.coffee +5 -0
  45. data/client/skr/screens/.gitkeep +0 -0
  46. data/client/skr/screens/Base.coffee +3 -0
  47. data/client/skr/screens/base/index.js +5 -0
  48. data/client/skr/screens/base/index.scss +9 -0
  49. data/client/skr/screens/base/layout.html +3 -0
  50. data/client/skr/screens/customer-maint/CustomerMaint.coffee +49 -0
  51. data/client/skr/screens/customer-maint/index.js +5 -0
  52. data/client/skr/screens/customer-maint/index.scss +9 -0
  53. data/client/skr/screens/customer-maint/layout.html +32 -0
  54. data/client/skr/styles.scss +1 -0
  55. data/client/skr/views/.gitkeep +0 -0
  56. data/client/skr/views/Base.coffee +5 -0
  57. data/config/database.yml +9 -0
  58. data/config/lanes.rb +7 -0
  59. data/config/routes.rb +39 -0
  60. data/config/screens.rb +17 -0
  61. data/config.ru +5 -0
  62. data/db/.gitkeep +0 -0
  63. data/db/migrate/20120110142845_create_skr_sequential_ids.rb +35 -0
  64. data/db/migrate/20140202185309_create_skr_gl_accounts.rb +15 -0
  65. data/db/migrate/20140202193316_create_skr_gl_periods.rb +16 -0
  66. data/db/migrate/20140202193318_create_skr_gl_transactions.rb +14 -0
  67. data/db/migrate/20140202193319_create_skr_gl_postings.rb +16 -0
  68. data/db/migrate/20140202193700_create_skr_gl_manual_entries.rb +13 -0
  69. data/db/migrate/20140213040608_create_skr_payment_terms.rb +16 -0
  70. data/db/migrate/20140220031700_create_skr_addresses.rb +19 -0
  71. data/db/migrate/20140220031800_create_skr_locations.rb +19 -0
  72. data/db/migrate/20140220190836_create_skr_vendors.rb +22 -0
  73. data/db/migrate/20140220203029_create_skr_customers.rb +22 -0
  74. data/db/migrate/20140224034759_create_skr_skus.rb +22 -0
  75. data/db/migrate/20140225032853_create_skr_sku_locs.rb +21 -0
  76. data/db/migrate/20140320030501_create_skr_uoms.rb +19 -0
  77. data/db/migrate/20140321031604_create_skr_sku_vendors.rb +18 -0
  78. data/db/migrate/20140322012143_create_skr_ia_reasons.rb +14 -0
  79. data/db/migrate/20140322014401_create_skr_inventory_adjustments.rb +16 -0
  80. data/db/migrate/20140322023453_create_skr_ia_lines.rb +18 -0
  81. data/db/migrate/20140322035024_create_skr_sku_trans.rb +21 -0
  82. data/db/migrate/20140322223912_create_skr_sales_orders.rb +27 -0
  83. data/db/migrate/20140322223920_create_skr_so_lines.rb +25 -0
  84. data/db/migrate/20140323001446_create_so_details_view.rb +81 -0
  85. data/db/migrate/20140327202102_create_skr_purchase_orders.rb +20 -0
  86. data/db/migrate/20140327202107_create_skr_po_lines.rb +25 -0
  87. data/db/migrate/20140327202207_create_skr_pick_tickets.rb +16 -0
  88. data/db/migrate/20140327202209_create_skr_pt_lines.rb +23 -0
  89. data/db/migrate/20140327224000_create_skr_invoices.rb +25 -0
  90. data/db/migrate/20140327224002_create_skr_inv_lines.rb +23 -0
  91. data/db/migrate/20140330232808_create_skr_sku_loc_details_view.rb +31 -0
  92. data/db/migrate/20140330232810_create_skr_sku_qty_details_view.rb +48 -0
  93. data/db/migrate/20140400164729_create_skr_vouchers.rb +22 -0
  94. data/db/migrate/20140400164733_create_skr_vo_lines.rb +21 -0
  95. data/db/migrate/20140401164729_create_skr_po_receipt.rb +16 -0
  96. data/db/migrate/20140401164740_create_skr_por_line.rb +21 -0
  97. data/db/migrate/20140422024010_create_skr_inv_details_view.rb +42 -0
  98. data/db/schema.sql +2662 -0
  99. data/db/seed/chart_of_accounts.yml +168 -0
  100. data/db/seed/payment_terms.yml +60 -0
  101. data/db/seed.rb +29 -0
  102. data/lib/skr/access_roles.rb +28 -0
  103. data/lib/skr/concerns/acts_as_uom.rb +47 -0
  104. data/lib/skr/concerns/code_identifier.rb +43 -0
  105. data/lib/skr/concerns/gl_tran_extensions.rb +18 -0
  106. data/lib/skr/concerns/has_gl_transaction.rb +67 -0
  107. data/lib/skr/concerns/has_sku_loc_lines.rb +47 -0
  108. data/lib/skr/concerns/immutable_model.rb +32 -0
  109. data/lib/skr/concerns/inv_extensions.rb +24 -0
  110. data/lib/skr/concerns/is_order_like.rb +47 -0
  111. data/lib/skr/concerns/is_sku_loc_line.rb +65 -0
  112. data/lib/skr/concerns/locked_fields.rb +84 -0
  113. data/lib/skr/concerns/pt_extensions.rb +22 -0
  114. data/lib/skr/concerns/random_hash_code.rb +40 -0
  115. data/lib/skr/concerns/so_extensions.rb +30 -0
  116. data/lib/skr/concerns/state_machine.rb +61 -0
  117. data/lib/skr/concerns/visible_id_identifier.rb +53 -0
  118. data/lib/skr/configuration.rb +68 -0
  119. data/lib/skr/db/migration_helpers.rb +178 -0
  120. data/lib/skr/extension.rb +23 -0
  121. data/lib/skr/model.rb +19 -0
  122. data/lib/skr/models/address.rb +97 -0
  123. data/lib/skr/models/business_entity.rb +29 -0
  124. data/lib/skr/models/customer.rb +35 -0
  125. data/lib/skr/models/gl_account.rb +56 -0
  126. data/lib/skr/models/gl_manual_entry.rb +31 -0
  127. data/lib/skr/models/gl_period.rb +13 -0
  128. data/lib/skr/models/gl_posting.rb +54 -0
  129. data/lib/skr/models/gl_transaction.rb +175 -0
  130. data/lib/skr/models/ia_line.rb +129 -0
  131. data/lib/skr/models/ia_reason.rb +16 -0
  132. data/lib/skr/models/inv_line.rb +90 -0
  133. data/lib/skr/models/inventory_adjustment.rb +60 -0
  134. data/lib/skr/models/invoice.rb +159 -0
  135. data/lib/skr/models/location.rb +31 -0
  136. data/lib/skr/models/payment_term.rb +30 -0
  137. data/lib/skr/models/pick_ticket.rb +71 -0
  138. data/lib/skr/models/po_line.rb +69 -0
  139. data/lib/skr/models/po_receipt.rb +51 -0
  140. data/lib/skr/models/por_line.rb +80 -0
  141. data/lib/skr/models/pt_line.rb +74 -0
  142. data/lib/skr/models/purchase_order.rb +112 -0
  143. data/lib/skr/models/sales_order.rb +159 -0
  144. data/lib/skr/models/sequential_id.rb +23 -0
  145. data/lib/skr/models/sku.rb +99 -0
  146. data/lib/skr/models/sku_loc.rb +94 -0
  147. data/lib/skr/models/sku_tran.rb +111 -0
  148. data/lib/skr/models/sku_vendor.rb +26 -0
  149. data/lib/skr/models/so_line.rb +159 -0
  150. data/lib/skr/models/uom.rb +63 -0
  151. data/lib/skr/models/user_proxy.rb +60 -0
  152. data/lib/skr/models/vendor.rb +33 -0
  153. data/lib/skr/models/vo_line.rb +35 -0
  154. data/lib/skr/models/voucher.rb +119 -0
  155. data/lib/skr/standard_pricing_provider.rb +14 -0
  156. data/lib/skr/version.rb +3 -0
  157. data/lib/skr.rb +18 -0
  158. data/lib/stockor.rb +4 -0
  159. data/lib/tasks/debug-activity.rake +58 -0
  160. data/log/test.log +0 -0
  161. data/spec/fixtures/skr/address.yml +2 -0
  162. data/spec/fixtures/skr/customer.yml +2 -0
  163. data/spec/fixtures/skr/gl_account.yml +2 -0
  164. data/spec/fixtures/skr/gl_manual_entry.yml +2 -0
  165. data/spec/fixtures/skr/gl_period.yml +2 -0
  166. data/spec/fixtures/skr/gl_posting.yml +2 -0
  167. data/spec/fixtures/skr/gl_transaction.yml +2 -0
  168. data/spec/fixtures/skr/ia_line.yml +2 -0
  169. data/spec/fixtures/skr/ia_reason.yml +2 -0
  170. data/spec/fixtures/skr/inv_line.yml +2 -0
  171. data/spec/fixtures/skr/inventory_adjustment.yml +2 -0
  172. data/spec/fixtures/skr/invoice.yml +2 -0
  173. data/spec/fixtures/skr/location.yml +2 -0
  174. data/spec/fixtures/skr/payment_term.yml +2 -0
  175. data/spec/fixtures/skr/pick_ticket.yml +2 -0
  176. data/spec/fixtures/skr/po_line.yml +2 -0
  177. data/spec/fixtures/skr/po_receipt.yml +2 -0
  178. data/spec/fixtures/skr/por_line.yml +2 -0
  179. data/spec/fixtures/skr/pt_line.yml +2 -0
  180. data/spec/fixtures/skr/purchase_order.yml +2 -0
  181. data/spec/fixtures/skr/sales_order.yml +2 -0
  182. data/spec/fixtures/skr/sku.yml +2 -0
  183. data/spec/fixtures/skr/sku_loc.yml +2 -0
  184. data/spec/fixtures/skr/sku_tran.yml +2 -0
  185. data/spec/fixtures/skr/sku_vendor.yml +2 -0
  186. data/spec/fixtures/skr/so_line.yml +2 -0
  187. data/spec/fixtures/skr/uom.yml +2 -0
  188. data/spec/fixtures/skr/vendor.yml +2 -0
  189. data/spec/fixtures/skr/vo_line.yml +2 -0
  190. data/spec/fixtures/skr/voucher.yml +2 -0
  191. data/spec/skr/address.rb +10 -0
  192. data/spec/skr/concerns/code_identifier_spec.rb +45 -0
  193. data/spec/skr/customer.rb +10 -0
  194. data/spec/skr/gl_account.rb +10 -0
  195. data/spec/skr/gl_manual_entry.rb +10 -0
  196. data/spec/skr/gl_period.rb +10 -0
  197. data/spec/skr/gl_posting.rb +10 -0
  198. data/spec/skr/gl_transaction.rb +10 -0
  199. data/spec/skr/ia_line.rb +10 -0
  200. data/spec/skr/ia_reason.rb +10 -0
  201. data/spec/skr/inv_line.rb +10 -0
  202. data/spec/skr/inventory_adjustment.rb +10 -0
  203. data/spec/skr/invoice.rb +10 -0
  204. data/spec/skr/location.rb +10 -0
  205. data/spec/skr/models/AddressSpec.coffee +5 -0
  206. data/spec/skr/models/CustomerSpec.coffee +5 -0
  207. data/spec/skr/models/GlAccountSpec.coffee +5 -0
  208. data/spec/skr/models/GlManualEntrySpec.coffee +5 -0
  209. data/spec/skr/models/GlPeriodSpec.coffee +5 -0
  210. data/spec/skr/models/GlPostingSpec.coffee +5 -0
  211. data/spec/skr/models/GlTransactionSpec.coffee +5 -0
  212. data/spec/skr/models/IaLineSpec.coffee +5 -0
  213. data/spec/skr/models/IaReasonSpec.coffee +5 -0
  214. data/spec/skr/models/InvLineSpec.coffee +5 -0
  215. data/spec/skr/models/InventoryAdjustmentSpec.coffee +5 -0
  216. data/spec/skr/models/InvoiceSpec.coffee +5 -0
  217. data/spec/skr/models/LocationSpec.coffee +5 -0
  218. data/spec/skr/models/PaymentTermSpec.coffee +5 -0
  219. data/spec/skr/models/PickTicketSpec.coffee +5 -0
  220. data/spec/skr/models/PoLineSpec.coffee +5 -0
  221. data/spec/skr/models/PoReceiptSpec.coffee +5 -0
  222. data/spec/skr/models/PorLineSpec.coffee +5 -0
  223. data/spec/skr/models/PtLineSpec.coffee +5 -0
  224. data/spec/skr/models/PurchaseOrderSpec.coffee +5 -0
  225. data/spec/skr/models/SalesOrderSpec.coffee +5 -0
  226. data/spec/skr/models/SkuLocSpec.coffee +5 -0
  227. data/spec/skr/models/SkuSpec.coffee +5 -0
  228. data/spec/skr/models/SkuTranSpec.coffee +5 -0
  229. data/spec/skr/models/SkuVendorSpec.coffee +5 -0
  230. data/spec/skr/models/SoLineSpec.coffee +5 -0
  231. data/spec/skr/models/UomSpec.coffee +5 -0
  232. data/spec/skr/models/VendorSpec.coffee +5 -0
  233. data/spec/skr/models/VoLineSpec.coffee +5 -0
  234. data/spec/skr/models/VoucherSpec.coffee +5 -0
  235. data/spec/skr/payment_term.rb +10 -0
  236. data/spec/skr/pick_ticket.rb +10 -0
  237. data/spec/skr/po_line.rb +10 -0
  238. data/spec/skr/po_receipt.rb +10 -0
  239. data/spec/skr/por_line.rb +10 -0
  240. data/spec/skr/pt_line.rb +10 -0
  241. data/spec/skr/purchase_order.rb +10 -0
  242. data/spec/skr/sales_order.rb +10 -0
  243. data/spec/skr/screens/Base.coffee +7 -0
  244. data/spec/skr/screens/CustomerMaint.coffee +7 -0
  245. data/spec/skr/screens/vendor-maint/VendorMaintSpec.coffee +5 -0
  246. data/spec/skr/sku.rb +10 -0
  247. data/spec/skr/sku_loc.rb +10 -0
  248. data/spec/skr/sku_tran.rb +10 -0
  249. data/spec/skr/sku_vendor.rb +10 -0
  250. data/spec/skr/so_line.rb +10 -0
  251. data/spec/skr/spec_helper.rb +26 -0
  252. data/spec/skr/uom.rb +10 -0
  253. data/spec/skr/vendor.rb +10 -0
  254. data/spec/skr/views/AddressSpec.coffee +5 -0
  255. data/spec/skr/vo_line.rb +10 -0
  256. data/spec/skr/voucher.rb +10 -0
  257. data/stockor.gemspec +38 -0
  258. data/tmp/.gitkeep +0 -0
  259. metadata +414 -0
@@ -0,0 +1,159 @@
1
+ module Skr
2
+
3
+ # Invoices constitute a demand for payment for goods that have been delivered to a {Customer}.
4
+ # A invoice contains:
5
+ #
6
+ # * Customer contact information
7
+ # * An inventory location that goods will be taken from.
8
+ # * The customer provided {PurchaseOrder} Number
9
+ # * The Payment Terms that were extended. This will control how much time the Customer has to pay the invoice in full.
10
+ # * One or more SKUs, the quantity desired for each and the selling price for them.
11
+ #
12
+ # While an {Invoice} often originates with a {SalesOrder}, it does not have to.
13
+ # Sales that take place in a retail environment where the customer selects
14
+ # the goods and pays for them immediately do not require a sales order record.
15
+ #
16
+ # Once an invoice is saved, it immediately removes the SKUs from the {SkuLoc}
17
+ # and generates corresponding General Ledger entries debiting the asset account
18
+ # and crediting the customers receivables account.
19
+ #
20
+ # When payment is received against the Invoice,
21
+ # the receivables account is debited and the payments holding account is credited.
22
+ #
23
+ # invoice = Invoice.new( customer: Customer.find_by_code("ACME")
24
+ # invoice.lines.build({ sku: Sku.find_by_code('LABOR'), qty: 1, price: 8.27 })
25
+ # invoice.save
26
+
27
+ class Invoice < Skr::Model
28
+
29
+ has_visible_id
30
+ has_random_hash_code
31
+ has_gl_transaction
32
+ is_order_like
33
+ has_additional_events :amount_paid_change
34
+
35
+ belongs_to :sales_order, export: true
36
+ belongs_to :customer, export: true
37
+ belongs_to :location, export: true
38
+ belongs_to :terms, class_name: 'Skr::PaymentTerm', export: true
39
+ belongs_to :pick_ticket, inverse_of: :invoice, export: true
40
+ belongs_to :billing_address, class_name: 'Skr::Address', export: { writable: true }
41
+ belongs_to :shipping_address, class_name: 'Skr::Address', export: { writable: true }
42
+
43
+ has_many :gl_transactions, :as=>:source
44
+
45
+ has_many :lines, -> { order(:position) }, class_name: 'Skr::InvLine', inverse_of: :invoice,
46
+ extend: Concerns::INV::Lines, export: { writable: true }
47
+
48
+ before_save :maybe_mark_paid
49
+
50
+ before_validation :set_defaults, on: :create
51
+
52
+ validates :customer, :location, set: true
53
+ validate :ensure_location_matches_so
54
+
55
+ scope :open_for_customer, lambda{ | customer |
56
+ where([ "customer_id=? and state != 'paid'", customer.is_a?(Customer) ? customer.id : customer ])
57
+ }, export: true
58
+
59
+ scope :with_details, lambda { |should_use=true |
60
+ compose_query_using_detail_view( view: 'inv_details', join_to: 'invoice_id' )
61
+ }, export: true
62
+
63
+ state_machine do
64
+ state :open, initial: true
65
+ state :paid
66
+ state :partial
67
+ event :mark_paid do
68
+ transitions from: [:open,:partial], to: :paid
69
+ before :apply_balances
70
+ end
71
+ event :mark_partial do
72
+ transitions from: [:open,:partial], to: :partial
73
+ before :apply_balances
74
+ end
75
+ end
76
+
77
+ def initialize(attributes = {})
78
+ super
79
+ self.invoice_date = Date.today
80
+ end
81
+
82
+ # @return [BigDecimal] total - amount_paid
83
+ def unpaid_amount
84
+ self.total - amount_paid
85
+ end
86
+
87
+ # @return [Boolean] is the invoice paid in full
88
+ def fully_paid?
89
+ unpaid_amount <= 0
90
+ end
91
+
92
+ private
93
+
94
+ # attributes for GlTransaction
95
+ def attributes_for_gl_transaction
96
+ { location: location, source: self,
97
+ description: "INV #{self.visible_id}" }
98
+ end
99
+
100
+ # set the state if the amount_paid was changed
101
+ def maybe_mark_paid
102
+ return unless amount_paid_changed?
103
+ if self.fully_paid? && self.may_mark_paid?
104
+ self.state_event = 'mark_paid'
105
+ elsif self.amount_paid > 0 && self.may_mark_partial?
106
+ self.state_event = 'mark_partial'
107
+ end
108
+ end
109
+
110
+ def apply_balances
111
+ return unless amount_paid_changed?
112
+ change = amount_paid - amount_paid_was
113
+
114
+ Skr::Core.logger.debug "Applying payment #{amount_paid} changed: #{change}"
115
+
116
+ return if change.zero?
117
+
118
+ GlTransaction.push_or_save(
119
+ owner: self, amount: change,
120
+ debit: customer.gl_receivables_account, credit: GlAccount.default_for(:deposit_holding)
121
+ )
122
+ fire_event( :amount_paid_change )
123
+ true
124
+ end
125
+
126
+
127
+ def set_defaults
128
+
129
+ if pick_ticket
130
+ self.location = pick_ticket.location
131
+ self.sales_order = pick_ticket.sales_order
132
+ end
133
+
134
+ if sales_order
135
+ self.terms ||= sales_order.terms
136
+ self.customer = sales_order.customer
137
+ self.po_num = sales_order.po_num if self.po_num.blank?
138
+ self.billing_address = sales_order.billing_address if self.billing_address.blank?
139
+ self.shipping_address = sales_order.shipping_address if self.shipping_address.blank?
140
+ self.options.merge!(sales_order.options)
141
+ end
142
+
143
+ if customer
144
+ self.billing_address = customer.billing_address if self.billing_address.blank?
145
+ self.shipping_address = customer.shipping_address if self.shipping_address.blank?
146
+ end
147
+
148
+ end
149
+
150
+ def ensure_location_matches_so
151
+ if sales_order && location != sales_order.location
152
+ self.errors.add(:location, "#{location.code} must match location that order was taken on (#{sales_order.location.code})")
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+
159
+ end # Skr module
@@ -0,0 +1,31 @@
1
+ module Skr
2
+
3
+ # A location that holds inventory
4
+ class Location < Skr::Model
5
+
6
+ has_code_identifier :from=>'name'
7
+
8
+ belongs_to :address, export: { writable: true }
9
+
10
+ has_many :sku_locs
11
+
12
+ validates :gl_branch_code, :presence => true, :numericality=>true, :length=>{:is=>2}
13
+
14
+ locked_fields :gl_branch_code
15
+
16
+ before_validation :set_defaults, :on=>:create
17
+
18
+ # @return [Location] the location that's specified by {Skr::Core::Configuration#default_location_code}
19
+ def self.default
20
+ Location.find_by_code( Skr.config.default_location_code )
21
+ end
22
+
23
+ private
24
+
25
+ def set_defaults
26
+ self.gl_branch_code ||= Skr.config.default_branch_code
27
+ true
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module Skr
2
+
3
+ # A pay
4
+ class PaymentTerm < Skr::Model
5
+
6
+ has_code_identifier
7
+
8
+ def discount
9
+ @discount_percnum ||= Core::Numbers::PercNum.new( read_attribute('discount_amount') )
10
+ end
11
+
12
+ def discount_amount=(value)
13
+ @discount_percnum = nil
14
+ super(value)
15
+ end
16
+
17
+ def immediate?
18
+ self.days.nil? || self.days.zero?
19
+ end
20
+
21
+ def discount_expires_at( start_date = Date.today )
22
+ ( start_date + self.discount_days.days ).to_date
23
+ end
24
+
25
+ def due_date_from( start_date = Date.today )
26
+ ( start_date + self.days.days ).to_date
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ module Skr
2
+
3
+ class PickTicket < Skr::Model
4
+
5
+ has_visible_id
6
+ is_order_like
7
+
8
+ belongs_to :sales_order
9
+ belongs_to :invoice, inverse_of: :pick_ticket, listen: { create: 'on_invoice' }
10
+ belongs_to :location
11
+
12
+ has_one :customer, through: :sales_order, export: true
13
+ has_one :terms, through: :sales_order
14
+
15
+ has_many :lines, ->{ order(:position) }, class_name: 'Skr::PtLine', inverse_of: :pick_ticket,
16
+ extend: Concerns::PT::Lines, export: { writable: true }
17
+
18
+ scope :with_details, lambda { | *args |
19
+ compose_query_using_detail_view( view: 'pt_details', join_to: 'pick_tickets_id' )
20
+ }, export: true
21
+
22
+ delegate :bill_addr, :to=>:sales_order
23
+
24
+ # If true, the PickTicket (and it's lines) will be marked as complete once it's saved
25
+ whitelist_attributes :mark_complete
26
+
27
+ before_update :check_for_mark_completed
28
+
29
+ validates :sales_order, set: true
30
+ validates :lines, presence: true
31
+ export_methods :ship_addr, :bill_addr
32
+
33
+ def ship_addr
34
+ sales_order.ship_addr.blank? ? sales_order.bill_addr : sales_order.ship_addr
35
+ end
36
+
37
+ def is_tax_exempt?
38
+ self.sales_order.is_tax_exempt?
39
+ end
40
+
41
+ def is_other_charge_locked?
42
+ return is_complete
43
+ end
44
+
45
+ def cancel!
46
+ update_attributes({ :is_complete=> true })
47
+ lines.each do | line |
48
+ line.update_attributes :is_complete=>true
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def check_for_mark_completed
55
+ return unless self.mark_complete
56
+ assign_attributes :is_complete=>true
57
+ lines.each do | line |
58
+ line.update_attributes :is_complete=>true
59
+ end
60
+ true
61
+ end
62
+
63
+
64
+ def on_invoice(inv)
65
+ self.update_attributes is_complete: true, shipped_at: Time.now
66
+ end
67
+
68
+
69
+ end
70
+
71
+ end # Skr module
@@ -0,0 +1,69 @@
1
+ module Skr
2
+
3
+ class PoLine < Skr::Model
4
+
5
+ acts_as_uom
6
+ is_sku_loc_line parent: 'purchase_order'
7
+
8
+ belongs_to :purchase_order, :inverse_of=>:lines
9
+ belongs_to :sku_loc, export: true
10
+ belongs_to :sku_vendor, export: true
11
+
12
+ has_one :sku, :through => :sku_loc, export: true
13
+
14
+ has_many :receipts, class_name: 'Skr::PorLine',
15
+ :inverse_of=>:po_line, listen: { create: :update_qty_received! }
16
+
17
+ validates :purchase_order, :sku_loc, set: true
18
+ validates :description, :sku_code, presence: true
19
+ validates :qty, :price, numericality: true, allow_nil: false
20
+
21
+ locked_fields :qty, :qty_received
22
+
23
+ scope :incomplete, ->{ where('qty - qty_received - qty_canceled != 0' ) }
24
+
25
+ before_validation :set_defaults, :on=>:create
26
+
27
+ def qty_unreceived
28
+ qty - qty_received - qty_canceled
29
+ end
30
+
31
+ def complete?
32
+ qty_unreceived.zero?
33
+ end
34
+
35
+ def update_qty_received!( receipt=nil )
36
+ unlock_fields :qty_received do
37
+ self.qty_received = receipts.sum(:qty)
38
+ self.save( :validate => false )
39
+ if self.complete?
40
+ self.purchase_order.set_maybe_completed!
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def set_defaults
48
+ if sku_loc && sku_vendor.nil?
49
+ self.sku_vendor = sku_loc.sku.sku_vendors.for_vendor( purchase_order.vendor )
50
+ elsif sku_vendor && sku_loc.nil?
51
+ self.sku_loc = sku_vendor.sku.sku_locs.find_or_create_for( purchase_order.location )
52
+ end
53
+ if sku_loc
54
+ self.sku_code ||= sku_loc.sku.code
55
+ self.description ||= sku_loc.sku.description
56
+ end
57
+ if sku_vendor
58
+ self.part_code ||= sku_vendor.part_code
59
+ self.price ||= sku_vendor.cost
60
+ self.uom_code ||= sku_vendor.uom_code
61
+ self.uom_size ||= sku_vendor.uom_size
62
+ end
63
+ true
64
+ end
65
+
66
+
67
+ end
68
+
69
+ end # Skr module
@@ -0,0 +1,51 @@
1
+ module Skr
2
+
3
+ # Is a record of an inventory receipt
4
+ # A {PurchaseOrder} can have one or more of them
5
+
6
+ class PoReceipt < Skr::Model
7
+
8
+ has_visible_id
9
+ has_sku_loc_lines
10
+ has_gl_transaction
11
+
12
+ belongs_to :purchase_order, export: true
13
+ belongs_to :vendor, export: true
14
+ belongs_to :location, export: true
15
+
16
+ has_one :gl_transaction, :as=>:source
17
+
18
+ has_many :lines, :class_name=>'Skr::PorLine', export: { writable: true }, inverse_of: :po_receipt
19
+
20
+ validates :freight, numericality: true
21
+ validates :purchase_order, :location, presence: true
22
+
23
+ before_create :record_freight, if: ->{ freight.nonzero? }
24
+ after_create :logit
25
+
26
+ def purchase_order=(po)
27
+ super
28
+ self.location ||= purchase_order.location
29
+ self.vendor = purchase_order.vendor
30
+ end
31
+
32
+ private
33
+
34
+ def attributes_for_gl_transaction
35
+ { location: location, source: self,
36
+ description: "PO RECPT #{self.visible_id}" }
37
+ end
38
+
39
+ def logit
40
+ end
41
+
42
+ def record_freight
43
+ GlTransaction.current.add_posting( amount: self.freight,
44
+ debit: GlAccount.default_for( :inventory_receipts_clearing ),
45
+ credit: vendor.gl_freight_account
46
+ )
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,80 @@
1
+ module Skr
2
+
3
+ class PorLine < Skr::Model
4
+
5
+ acts_as_uom
6
+ is_sku_loc_line parent: 'po_receipt'
7
+
8
+ belongs_to :po_receipt
9
+ belongs_to :sku_loc, export: true
10
+ belongs_to :po_line, export: true
11
+ belongs_to :sku_vendor, export: true
12
+
13
+ has_one :sku, :through => :sku_loc, export: true
14
+
15
+ has_many :sku_trans, :as=>:origin, validate: true
16
+
17
+ before_create :adjust_inventory
18
+ # after_update :adjust_gl
19
+ validates :qty, numericality: { greater_than: 0 }
20
+
21
+ whitelist_attributes :auto_allocate
22
+
23
+ def po_line=(pol)
24
+ super
25
+ %w{ sku_code part_code description uom_code uom_size }.each do | attr |
26
+ self[ attr ] = po_line[ attr ]
27
+ end
28
+ self.sku_loc = pol.sku_loc
29
+ self.sku_vendor = pol.sku_vendor
30
+ self.uom = pol.uom
31
+ self.price = pol.price
32
+ self.qty = pol.qty_unreceived
33
+ end
34
+
35
+ private
36
+
37
+ def adjust_inventory
38
+ Core.logger.debug( "Receiving #{self.ea_qty} into stock" )
39
+ tran = self.sku_trans.build({
40
+ origin: self, qty: self.qty,
41
+ sku_loc: po_line.sku_loc,
42
+ origin_description: "PO #{self.po_line.purchase_order.visible_id}",
43
+ cost: self.extended_price, uom: self.uom,
44
+ credit_gl_account: self.sku.gl_asset_account,
45
+ debit_gl_account: GlAccount.default_for( :inventory_receipts_clearing )
46
+ })
47
+ if self.auto_allocate
48
+ tran.allocate_after_save = true
49
+ end
50
+ true
51
+ end
52
+
53
+ def ensure_qty_is_less_than_unreceived
54
+ if qty_changed? && po_line && qty > po_line.qty_unreceived
55
+ errors.add(:qty,"#{qty} must be less than unreceived qty of #{po_line.qty_unreceived}")
56
+ end
57
+ end
58
+
59
+ def adjust_gl
60
+ diff = price - price_was
61
+ return if diff.zero?
62
+
63
+ tran = self.sku_trans.build({ :origin=>self, :qty => 0, :sku_loc=>po_line.sku_loc,
64
+ :credit_gl_account => self.sku.gl_asset_account,
65
+ :uom_size=>self.uom_size, :uom_code=>self.uom_code })
66
+
67
+ tran.cost = diff * qty
68
+
69
+ tran.debit_gl_account = if voucher.confirmed?
70
+ voucher.vendor.gl_payables_account
71
+ else # otherwise it's in the clearing account
72
+ GlAccount.default_for( :inventory_receipts_clearing )
73
+ end
74
+ tran.save!
75
+
76
+ end
77
+
78
+
79
+ end
80
+ end
@@ -0,0 +1,74 @@
1
+ module Skr
2
+
3
+ class PtLine < Skr::Model
4
+
5
+ acts_as_uom
6
+ is_sku_loc_line parent: 'pick_ticket'
7
+
8
+ attr_accessor :qty_to_ship
9
+
10
+ belongs_to :pick_ticket, export: true
11
+ belongs_to :sku_loc, export: true
12
+ belongs_to :so_line
13
+
14
+ has_one :inv_line, inverse_of: :pt_line, listen: { save: :update_from_inv_line }
15
+ has_one :sales_order, :through=>:pick_ticket, export: true
16
+ has_one :sku, :through => :sku_loc, export: true
17
+
18
+ scope :picking, ->{ where({ :is_complete=>false }) }
19
+
20
+ delegate_and_export_field :pick_ticket, :visible_id
21
+ delegate_and_export_field :sales_order, :visible_id
22
+
23
+ before_create :set_defaults#, on: :create
24
+
25
+ def cancel!
26
+ self.update_attributes! :is_complete=>true
27
+ self.pick_ticket.maybe_cancel
28
+ end
29
+
30
+ def is_invoiceable?
31
+ ! self.is_complete? && self.qty_to_ship.to_i > 0
32
+ end
33
+
34
+ def total
35
+ self.price * ( self.qty_to_ship || self.qty )
36
+ end
37
+
38
+ private
39
+
40
+ def update_from_inv_line( inv_line )
41
+ self.update_attributes( qty_invoiced: inv_line.qty )
42
+ end
43
+
44
+ def set_defaults
45
+
46
+ if self.so_line.blank?
47
+ self.so_line = self.pick_ticket.sales_order.lines.where({ sku_loc_id: self.sku_loc_id }).first
48
+ end
49
+
50
+ if self.so_line.blank?
51
+ self.so_line = self.pick_ticket.sales_order.lines.create({
52
+ sku_loc: self.sku_loc, qty: self.qty, price: self.price,
53
+ uom: self.uom, description: self.description
54
+ })
55
+ unless self.so_line.valid?
56
+ self.errors.add(:so_line, "cannot be created because #{self.so_line.errors.full_messages.join(', ')}" )
57
+ return false
58
+ end
59
+ end
60
+
61
+ self.sku_loc ||= so_line.sku_loc
62
+ self.bin ||= sku_loc.bin
63
+ self.price ||= so_line.price
64
+ self.sku_code = sku_loc.sku.code if self.sku_code.blank?
65
+ self.description = so_line.description if self.description.blank?
66
+ self.uom = so_line.uom if self.uom.blank?
67
+ true
68
+ end
69
+
70
+
71
+ end
72
+
73
+
74
+ end # Skr module
@@ -0,0 +1,112 @@
1
+ module Skr
2
+
3
+ # A Purchase Order (often abbreviated as PO)
4
+ # is a record of a request to purchase a product from a Vendor.
5
+ # It contains one or more {Sku}, the quantity desired for each and the price offered for them.
6
+ #
7
+ # A Purchase Order progresses through set stages:
8
+ #
9
+ # * It starts of as "saved"
10
+ # This state indicates that the PO has been saved but no further action has been under-taken.
11
+ # * Once it has been sent to the Vendor, it transitions to Sent.
12
+ # * On reciept of a confirmation from the vendor it becomes Confirmed At this point the PO may be
13
+ # considered a binding agreement with the {Vendor}.
14
+ # * When the ordered SKUs are received, the PO will be marked as either Partial or Complete.
15
+ # * A {PoReceipt} is then created from the Purchase Order.
16
+
17
+ class PurchaseOrder < Skr::Model
18
+
19
+ has_visible_id
20
+
21
+ belongs_to :terms, export: true, class_name: 'Skr::PaymentTerm'
22
+ belongs_to :vendor, export: true
23
+ belongs_to :location, export: true
24
+ belongs_to :ship_addr, :class_name=>'Skr::Address', export: { writable: true }
25
+
26
+ has_many :lines, ->{ order(:position) }, :class_name=>'Skr::PoLine', :inverse_of=>:purchase_order, export: {writable:true}
27
+ has_many :receipts, class_name: 'Skr::PoReceipt'
28
+
29
+ before_validation :set_defaults, :on=>:create
30
+
31
+ validates :vendor, :location, :terms, :presence=>true
32
+
33
+ delegate_and_export :vendor_code, :vendor_name
34
+ delegate_and_export :location_code
35
+ delegate_and_export :terms_code, :terms_description
36
+
37
+ export_methods :total
38
+
39
+ blacklist_attributes :receiving_completed_at
40
+
41
+ after_save :set_maybe_completed!
42
+
43
+ export_join_tables :details
44
+ export_scope :with_details, lambda { |should_use=true |
45
+ joins('join po_details as details on details.purchase_order_id = purchase_orders.id')
46
+ .select('purchase_orders.*, details.*') if should_use
47
+ }
48
+
49
+ export_scope :only_incoming, lambda { |should_use=true|
50
+ with_details.where("state !='received'") if should_use
51
+ }
52
+
53
+ state_machine do
54
+ state :open, initial: true
55
+ state :received
56
+
57
+ event :mark_received do
58
+ transitions from: :open, to: :received
59
+ before do
60
+ self.receiving_completed_at = Time.now
61
+ end
62
+ end
63
+ end
64
+
65
+ def after_message_transmitted( msg )
66
+ self.mark_transmitted if self.can_mark_transmitted?
67
+ end
68
+
69
+ def set_maybe_completed!
70
+ self.mark_received! unless received? or self.lines.incomplete.any?
71
+ end
72
+
73
+ def to_shipping_address
74
+ Address.copy_from(self,'ship_')
75
+ end
76
+
77
+ def set_message_fields( msg )
78
+ msg.recipient = self.vendor.payments_address.email_with_name if msg.recipient.blank?
79
+ msg.subject = "Purchase Order # #{self.visible_id}"
80
+ attach = msg.attachments.build
81
+ attach.set_type_to_pdf
82
+ pdf = self.as_pdf
83
+ attach.file = pdf
84
+ end
85
+
86
+ def as_pdf( opts={} )
87
+ defaults = { :purchase_order => self, :include_received=>false }
88
+ Latex::Runner.new( 'purchase_order', defaults.merge( opts ) ).pdf( "PurchaseOrder_#{self.visible_id}.pdf" )
89
+ end
90
+
91
+ def total
92
+ if total = self.read_attribute('total')
93
+ BigDecimal.new(total)
94
+ elsif self.association(:lines).loaded?
95
+ self.lines.inject(0){|sum,line| sum + line.total }
96
+ else
97
+ BigDecimal.new( self.lines.sum('price*qty') )
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def set_defaults
104
+ self.location ||= Location.default
105
+ self.order_date ||= Date.today
106
+ self.terms ||= self.vendor.terms if self.vendor
107
+ self.ship_addr = self.location.address if self.ship_addr.blank? && self.location
108
+ end
109
+ end
110
+
111
+
112
+ end # Skr module